关键节点

This commit is contained in:
2026-04-25 16:46:01 +08:00
parent 6002d86a7e
commit ef41c44464
516 changed files with 6175 additions and 12401 deletions

View File

@@ -0,0 +1,585 @@
#include "Platform/Win32/Windowing/EditorWindowMessageDispatcher.h"
#include "Platform/Win32/Chrome/BorderlessWindowChrome.h"
#include "Platform/Win32/Chrome/EditorWindowChromeController.h"
#include "Platform/Win32/Runtime/EditorWindowInputController.h"
#include "Platform/Win32/Windowing/EditorWindow.h"
#include "Platform/Win32/Runtime/EditorWindowFrameDriver.h"
#include "Platform/Win32/Runtime/EditorWindowRuntimeController.h"
#include "Platform/Win32/Windowing/EditorWindowPointerCapture.h"
#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h"
#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h"
#include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h"
#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h"
#include <cstdint>
#include <sstream>
#include <utility>
namespace XCEngine::UI::Editor::App {
namespace {
constexpr UINT kMessageNcUaDrawCaption = 0x00AEu;
constexpr UINT kMessageNcUaDrawFrame = 0x00AFu;
std::string DescribeHwnd(HWND hwnd) {
std::ostringstream stream = {};
stream << "0x" << std::hex << std::uppercase
<< reinterpret_cast<std::uintptr_t>(hwnd);
return stream.str();
}
std::string DescribeHostWindows(const EditorWindowHostRuntime& hostRuntime) {
std::ostringstream stream = {};
const auto& windows = hostRuntime.GetWindows();
stream << "count=" << windows.size() << " [";
bool first = true;
for (const std::unique_ptr<EditorWindow>& window : windows) {
if (!first) {
stream << ", ";
}
first = false;
if (window == nullptr) {
stream << "<null>";
continue;
}
stream << window->GetWindowId()
<< "{hwnd=" << DescribeHwnd(window->GetHwnd())
<< ",primary=" << (window->IsPrimary() ? '1' : '0')
<< ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState())
<< '}';
}
stream << ']';
return stream.str();
}
} // namespace
struct EditorWindowMessageDispatcher::DispatchContext {
HWND hwnd = nullptr;
EditorWindowHostRuntime& hostRuntime;
EditorWindowLifecycleCoordinator& lifecycleCoordinator;
EditorUtilityWindowCoordinator& utilityCoordinator;
EditorWindowWorkspaceCoordinator& workspaceCoordinator;
EditorWindow& window;
};
void EditorWindowMessageDispatcher::DispatchWindowFrameTransferRequests(
const DispatchContext& context,
const EditorWindowFrameTransferRequests& transferRequests) {
context.workspaceCoordinator.HandleWindowFrameTransferRequests(
context.window,
transferRequests);
context.utilityCoordinator.HandleWindowFrameTransferRequests(
context.window,
transferRequests);
}
void EditorWindowMessageDispatcher::FinalizeImmediateFrame(
const DispatchContext& context,
const EditorWindowFrameTransferRequests& transferRequests) {
context.workspaceCoordinator.RefreshWindowPresentation(context.window);
if (!transferRequests.HasPendingRequests()) {
return;
}
DispatchWindowFrameTransferRequests(context, transferRequests);
}
void EditorWindowMessageDispatcher::FlushQueuedCompletedImmediateFrame(
const DispatchContext& context) {
if (!context.window.HasQueuedCompletedImmediateFrame()) {
return;
}
FinalizeImmediateFrame(
context,
context.window.ConsumeQueuedCompletedImmediateFrameTransferRequests());
}
void EditorWindowMessageDispatcher::RenderAndHandleWindowFrame(const DispatchContext& context) {
FinalizeImmediateFrame(
context,
EditorWindowFrameDriver::DriveImmediateFrame(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive()));
}
bool EditorWindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) {
if (context.window.m_inputController->IsTrackingMouseLeave()) {
return true;
}
TRACKMOUSEEVENT trackMouseEvent = {};
trackMouseEvent.cbSize = sizeof(trackMouseEvent);
trackMouseEvent.dwFlags = TME_LEAVE;
trackMouseEvent.hwndTrack = context.hwnd;
if (!TrackMouseEvent(&trackMouseEvent)) {
return false;
}
context.window.m_inputController->SetTrackingMouseLeave(true);
return true;
}
bool EditorWindowMessageDispatcher::TryHandleChromeHoverConsumption(
const DispatchContext& context,
LPARAM lParam,
LRESULT& outResult) {
EditorWindowInputController& inputController = *context.window.m_inputController;
EditorWindowChromeController& chromeController = *context.window.m_chromeController;
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
context.window.m_runtime->TryGetInputFeedbackBinding();
if (!CanConsumeEditorWindowChromeHover(
inputController.GetPointerCaptureOwner(),
inputFeedbackBinding != nullptr &&
inputFeedbackBinding->HasShellInteractiveCapture(),
inputFeedbackBinding != nullptr &&
inputFeedbackBinding->HasHostedContentCapture())) {
return false;
}
const bool resizeHoverChanged = chromeController.UpdateResizeHover(context.window, lParam);
if (chromeController.UpdateChromeHover(context.window, lParam)) {
context.window.InvalidateHostWindow();
}
if (resizeHoverChanged) {
context.window.InvalidateHostWindow();
}
EnsureTrackingMouseLeave(context);
if (chromeController.GetHoveredBorderlessResizeEdge() !=
Host::BorderlessWindowResizeEdge::None) {
outResult = 0;
return true;
}
const Host::BorderlessWindowChromeHitTarget chromeHitTarget =
chromeController.HitTestChrome(context.window, lParam);
if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton ||
chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) {
outResult = 0;
return true;
}
return false;
}
bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage(
const DispatchContext& context,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
EditorWindowInputController& inputController = *context.window.m_inputController;
EditorWindowChromeController& chromeController = *context.window.m_chromeController;
switch (message) {
case WM_MOUSEMOVE:
if (CanRouteEditorWindowGlobalTabDragPointerMessages(
inputController.GetPointerCaptureOwner(),
context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) &&
context.workspaceCoordinator.HandleGlobalTabDragPointerMove(context.hwnd)) {
outResult = 0;
return true;
}
if (CanRouteEditorWindowBorderlessResizePointerMessages(
inputController.GetPointerCaptureOwner()) &&
chromeController.HandleResizePointerMove(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive())) {
outResult = 0;
return true;
}
if (CanRouteEditorWindowBorderlessChromePointerMessages(
inputController.GetPointerCaptureOwner()) &&
chromeController.HandleChromeDragRestorePointerMove(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive())) {
outResult = 0;
return true;
}
if (TryHandleChromeHoverConsumption(context, lParam, outResult)) {
return true;
}
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerMove,
::XCEngine::UI::UIPointerButton::None,
wParam,
lParam);
outResult = 0;
return true;
case WM_MOUSELEAVE:
inputController.SetTrackingMouseLeave(false);
chromeController.ClearResizeState(context.window);
chromeController.ClearChromeDragRestoreState(context.window);
chromeController.ClearChromeState(context.window);
context.window.QueuePointerLeaveEvent();
outResult = 0;
return true;
case WM_LBUTTONDOWN:
if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) {
context.workspaceCoordinator.EndGlobalTabDragSession();
}
if (chromeController.HandleResizeButtonDown(context.window, lParam)) {
outResult = 0;
return true;
}
if (chromeController.HandleChromeButtonDown(context.window, lParam)) {
outResult = 0;
return true;
}
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Left,
wParam,
lParam,
true);
outResult = 0;
return true;
case WM_RBUTTONDOWN:
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Right,
wParam,
lParam,
true);
outResult = 0;
return true;
case WM_MBUTTONDOWN:
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Middle,
wParam,
lParam,
true);
outResult = 0;
return true;
case WM_LBUTTONUP:
if (CanRouteEditorWindowGlobalTabDragPointerMessages(
inputController.GetPointerCaptureOwner(),
context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) &&
context.workspaceCoordinator.HandleGlobalTabDragPointerButtonUp(context.hwnd)) {
outResult = 0;
return true;
}
if (CanRouteEditorWindowBorderlessResizePointerMessages(
inputController.GetPointerCaptureOwner()) &&
chromeController.HandleResizeButtonUp(context.window)) {
outResult = 0;
return true;
}
if (CanRouteEditorWindowBorderlessChromePointerMessages(
inputController.GetPointerCaptureOwner()) &&
chromeController.HandleChromeButtonUp(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive(),
lParam)) {
outResult = 0;
return true;
}
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonUp,
::XCEngine::UI::UIPointerButton::Left,
wParam,
lParam);
outResult = 0;
return true;
case WM_RBUTTONUP:
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonUp,
::XCEngine::UI::UIPointerButton::Right,
wParam,
lParam);
outResult = 0;
return true;
case WM_MBUTTONUP:
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonUp,
::XCEngine::UI::UIPointerButton::Middle,
wParam,
lParam);
outResult = 0;
return true;
case WM_LBUTTONDBLCLK:
if (chromeController.HandleChromeDoubleClick(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive(),
lParam)) {
outResult = 0;
return true;
}
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Left,
wParam,
lParam);
outResult = 0;
return true;
case WM_RBUTTONDBLCLK:
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Right,
wParam,
lParam);
outResult = 0;
return true;
case WM_MBUTTONDBLCLK:
SetFocus(context.hwnd);
context.window.TryStartImmediateShellPointerCapture(lParam);
context.window.QueuePointerEvent(
::XCEngine::UI::UIInputEventType::PointerButtonDown,
::XCEngine::UI::UIPointerButton::Middle,
wParam,
lParam);
outResult = 0;
return true;
case WM_MOUSEWHEEL:
context.window.QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam);
outResult = 0;
return true;
default:
return false;
}
}
bool EditorWindowMessageDispatcher::TryDispatchWindowInputMessage(
const DispatchContext& context,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
EditorWindowInputController& inputController = *context.window.m_inputController;
EditorWindowChromeController& chromeController = *context.window.m_chromeController;
if (TryDispatchWindowPointerMessage(context, message, wParam, lParam, outResult)) {
return true;
}
switch (message) {
case WM_SETFOCUS:
inputController.SyncInputModifiersFromSystemState();
inputController.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained);
outResult = 0;
return true;
case WM_KILLFOCUS:
inputController.ResetInputModifiers();
inputController.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost);
outResult = 0;
return true;
case WM_CAPTURECHANGED:
if (reinterpret_cast<HWND>(lParam) != context.hwnd) {
inputController.ClearPointerCaptureOwner();
}
if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) &&
reinterpret_cast<HWND>(lParam) != context.hwnd) {
context.workspaceCoordinator.EndGlobalTabDragSession();
outResult = 0;
return true;
}
if (reinterpret_cast<HWND>(lParam) != context.hwnd &&
context.window.HasInteractiveCaptureState()) {
inputController.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost);
chromeController.ForceClearResizeState(context.window);
chromeController.ClearChromeDragRestoreState(context.window);
chromeController.ClearChromeState(context.window);
outResult = 0;
return true;
}
if (reinterpret_cast<HWND>(lParam) != context.hwnd) {
chromeController.ForceClearResizeState(context.window);
chromeController.ClearChromeDragRestoreState(context.window);
chromeController.ClearChromeState(context.window);
}
return false;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (wParam == VK_F12) {
context.window.m_runtime->RequestManualScreenshot("manual_f12");
}
inputController.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam);
outResult = 0;
return true;
case WM_KEYUP:
case WM_SYSKEYUP:
inputController.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyUp, wParam, lParam);
outResult = 0;
return true;
case WM_CHAR:
inputController.QueueCharacterEvent(wParam);
outResult = 0;
return true;
default:
return false;
}
}
bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage(
const DispatchContext& context,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
switch (message) {
case WM_DPICHANGED:
if (lParam == 0) {
return false;
}
context.window.OnDpiChanged(
static_cast<UINT>(LOWORD(wParam)),
*reinterpret_cast<const RECT*>(lParam));
RenderAndHandleWindowFrame(context);
outResult = 0;
return true;
case WM_ENTERSIZEMOVE:
context.window.OnEnterSizeMove();
outResult = 0;
return true;
case WM_EXITSIZEMOVE:
if (context.window.OnExitSizeMove()) {
RenderAndHandleWindowFrame(context);
}
outResult = 0;
return true;
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
const bool requiresImmediateFrame = context.window.OnResize(
static_cast<UINT>(LOWORD(lParam)),
static_cast<UINT>(HIWORD(lParam)));
if (requiresImmediateFrame) {
RenderAndHandleWindowFrame(context);
}
}
outResult = 0;
return true;
case WM_CLOSE:
context.lifecycleCoordinator.ExecuteCloseRequest(context.window);
outResult = 0;
return true;
case WM_PAINT:
FinalizeImmediateFrame(
context,
context.window.OnPaintMessage(
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive()));
outResult = 0;
return true;
case WM_ERASEBKGND:
outResult = 1;
return true;
case WM_DESTROY:
context.lifecycleCoordinator.HandleNativeWindowDestroyed(context.window);
outResult = 0;
return true;
default:
return false;
}
}
bool EditorWindowMessageDispatcher::TryDispatchWindowChromeMessage(
const DispatchContext& context,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
EditorWindowChromeController& chromeController = *context.window.m_chromeController;
switch (message) {
case WM_GETMINMAXINFO:
if (chromeController.HandleGetMinMaxInfo(context.window, lParam)) {
outResult = 0;
return true;
}
return false;
case WM_NCCALCSIZE:
outResult = chromeController.HandleNcCalcSize(context.window, wParam, lParam);
return true;
case WM_NCACTIVATE:
outResult = TRUE;
return true;
case WM_NCHITTEST:
outResult = HTCLIENT;
return true;
case WM_NCPAINT:
case kMessageNcUaDrawCaption:
case kMessageNcUaDrawFrame:
outResult = 0;
return true;
case WM_SYSCOMMAND:
if (chromeController.HandleSystemCommand(
context.window,
context.hostRuntime.GetEditorContext(),
context.workspaceCoordinator.IsGlobalTabDragActive(),
wParam)) {
outResult = 0;
return true;
}
return false;
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT && context.window.ApplyCurrentCursor()) {
outResult = TRUE;
return true;
}
return false;
default:
return false;
}
}
bool EditorWindowMessageDispatcher::TryDispatch(
HWND hwnd,
EditorWindowHostRuntime& hostRuntime,
EditorWindowLifecycleCoordinator& lifecycleCoordinator,
EditorUtilityWindowCoordinator& utilityCoordinator,
EditorWindowWorkspaceCoordinator& workspaceCoordinator,
EditorWindow& window,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
const DispatchContext context = {
.hwnd = hwnd,
.hostRuntime = hostRuntime,
.lifecycleCoordinator = lifecycleCoordinator,
.utilityCoordinator = utilityCoordinator,
.workspaceCoordinator = workspaceCoordinator,
.window = window,
};
bool handled = false;
if (TryDispatchWindowChromeMessage(context, message, wParam, lParam, outResult)) {
handled = true;
} else if (TryDispatchWindowLifecycleMessage(context, message, wParam, lParam, outResult)) {
handled = true;
} else if (TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult)) {
handled = true;
}
if (handled) {
FlushQueuedCompletedImmediateFrame(context);
}
return handled;
}
} // namespace XCEngine::UI::Editor::App