Files
XCEngine/new_editor/app/Platform/Win32/EditorWindowFrame.cpp

422 lines
15 KiB
C++
Raw Normal View History

#include "Platform/Win32/EditorWindow.h"
#include "Platform/Win32/EditorWindowConstants.h"
#include "Platform/Win32/EditorWindowInternalState.h"
#include "Platform/Win32/EditorWindowRuntimeInternal.h"
#include "Platform/Win32/EditorWindowStyle.h"
#include "Composition/EditorShellPointerInteraction.h"
#include "State/EditorContext.h"
#include <XCEngine/UI/DrawData.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
#include <utility>
namespace XCEngine::UI::Editor::App {
using namespace EditorWindowInternal;
using ::XCEngine::UI::UIDrawData;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIInputModifiers;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
using ::XCEngine::UI::UIRect;
namespace {
std::string DescribeInputEventType(const UIInputEvent& event) {
switch (event.type) {
case UIInputEventType::PointerMove: return "PointerMove";
case UIInputEventType::PointerEnter: return "PointerEnter";
case UIInputEventType::PointerLeave: return "PointerLeave";
case UIInputEventType::PointerButtonDown: return "PointerDown";
case UIInputEventType::PointerButtonUp: return "PointerUp";
case UIInputEventType::PointerWheel: return "PointerWheel";
case UIInputEventType::KeyDown: return "KeyDown";
case UIInputEventType::KeyUp: return "KeyUp";
case UIInputEventType::Character: return "Character";
case UIInputEventType::FocusGained: return "FocusGained";
case UIInputEventType::FocusLost: return "FocusLost";
default: return "Unknown";
}
}
bool HasPendingPointerStateReconciliationEvent(
const std::vector<UIInputEvent>& events) {
for (const UIInputEvent& event : events) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
case UIInputEventType::FocusLost:
return true;
case UIInputEventType::PointerLeave:
case UIInputEventType::KeyDown:
case UIInputEventType::KeyUp:
case UIInputEventType::Character:
case UIInputEventType::FocusGained:
case UIInputEventType::None:
default:
break;
}
}
return false;
}
std::uint8_t ButtonMask(UIPointerButton button) {
switch (button) {
case UIPointerButton::Left: return 1u << 0u;
case UIPointerButton::Right: return 1u << 1u;
case UIPointerButton::Middle: return 1u << 2u;
case UIPointerButton::X1: return 1u << 3u;
case UIPointerButton::X2: return 1u << 4u;
case UIPointerButton::None:
default:
return 0u;
}
}
std::uint8_t ButtonMaskFromModifiers(const UIInputModifiers& modifiers) {
std::uint8_t mask = 0u;
if (modifiers.leftMouse) {
mask |= ButtonMask(UIPointerButton::Left);
}
if (modifiers.rightMouse) {
mask |= ButtonMask(UIPointerButton::Right);
}
if (modifiers.middleMouse) {
mask |= ButtonMask(UIPointerButton::Middle);
}
if (modifiers.x1Mouse) {
mask |= ButtonMask(UIPointerButton::X1);
}
if (modifiers.x2Mouse) {
mask |= ButtonMask(UIPointerButton::X2);
}
return mask;
}
std::uint8_t ResolveExpectedShellCaptureButtons(
const EditorShellRuntime& shellRuntime) {
std::uint8_t expectedButtons = 0u;
const auto& shellState = shellRuntime.GetShellInteractionState();
const auto& dockHostState =
shellState.workspaceInteractionState.dockHostInteractionState;
if (dockHostState.splitterDragState.active ||
!dockHostState.activeTabDragNodeId.empty()) {
expectedButtons |= ButtonMask(UIPointerButton::Left);
}
for (const auto& panelState :
shellState.workspaceInteractionState.composeState.panelStates) {
const auto& inputBridgeState = panelState.viewportShellState.inputBridgeState;
if (inputBridgeState.captured) {
expectedButtons |= ButtonMask(inputBridgeState.captureButton);
}
}
return expectedButtons;
}
} // namespace
EditorWindowFrameTransferRequests EditorWindow::RenderFrame(
EditorContext& editorContext,
bool globalTabDragActive) {
if (!m_state->render.ready || m_state->window.hwnd == 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& drawList = drawData.EmplaceDrawList("XCEditorShell");
drawList.AddFilledRect(
UIRect(0.0f, 0.0f, width, height),
kShellSurfaceColor);
EditorWindowFrameTransferRequests transferRequests = {};
if (editorContext.IsValid()) {
transferRequests =
RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList);
} else {
RenderInvalidFrame(editorContext, drawList);
}
AppendBorderlessWindowChrome(drawList, width);
const Host::D3D12WindowRenderLoopPresentResult presentResult =
m_state->render.windowRenderLoop.Present(drawData);
if (!presentResult.warning.empty()) {
LogRuntimeTrace("present", presentResult.warning);
}
m_state->render.autoScreenshot.CaptureIfRequested(
m_state->render.renderer,
drawData,
pixelWidth,
pixelHeight,
presentResult.framePresented);
return transferRequests;
}
EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage(
EditorContext& editorContext,
bool globalTabDragActive) {
if (!m_state->render.ready || m_state->window.hwnd == nullptr) {
return {};
}
PAINTSTRUCT paintStruct = {};
BeginPaint(m_state->window.hwnd, &paintStruct);
const EditorWindowFrameTransferRequests transferRequests =
RenderFrame(editorContext, globalTabDragActive);
EndPaint(m_state->window.hwnd, &paintStruct);
return transferRequests;
}
UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
if (!IsBorderlessWindowEnabled()) {
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,
titleBarHeight,
clientWidthDips,
(std::max)(0.0f, clientHeightDips - titleBarHeight));
}
EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame(
EditorContext& editorContext,
bool globalTabDragActive,
const UIRect& workspaceBounds,
UIDrawList& drawList) {
SyncShellCapturedPointerButtonsFromSystemState();
std::vector<UIInputEvent> frameEvents = std::move(m_state->input.pendingEvents);
m_state->input.pendingEvents.clear();
if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) {
LogRuntimeTrace(
"input",
DescribeInputEvents(frameEvents) + " | " +
editorContext.DescribeWorkspaceState(
GetWorkspaceController(),
m_state->composition.shellRuntime.GetShellInteractionState()));
}
const Host::D3D12WindowRenderLoopFrameContext frameContext =
m_state->render.windowRenderLoop.BeginFrame();
if (!frameContext.warning.empty()) {
LogRuntimeTrace("viewport", frameContext.warning);
}
editorContext.AttachTextMeasurer(m_state->render.renderer);
const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip();
UIEditorWorkspaceController& workspaceController = GetMutableWorkspaceController();
m_state->composition.shellRuntime.Update(
editorContext,
workspaceController,
workspaceBounds,
frameEvents,
BuildCaptureStatusText(),
m_state->window.primary
? EditorShellVariant::Primary
: EditorShellVariant::DetachedWindow,
useDetachedTitleBarTabStrip,
useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f);
const UIEditorShellInteractionFrame& shellFrame =
m_state->composition.shellRuntime.GetShellFrame();
const UIEditorDockHostInteractionState& dockHostInteractionState =
m_state->composition.shellRuntime
.GetShellInteractionState()
.workspaceInteractionState
.dockHostInteractionState;
LogFrameInteractionTrace(editorContext, frameEvents, shellFrame);
const EditorWindowFrameTransferRequests transferRequests =
BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame);
ApplyShellRuntimePointerCapture();
for (const WorkspaceTraceEntry& entry :
m_state->composition.shellRuntime.GetTraceEntries()) {
LogRuntimeTrace(entry.channel, entry.message);
}
ApplyCurrentCursor();
m_state->composition.shellRuntime.Append(drawList);
if (frameContext.canRenderViewports) {
m_state->composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext);
}
return transferRequests;
}
void EditorWindow::RenderInvalidFrame(
EditorContext& editorContext,
UIDrawList& drawList) const {
drawList.AddText(
UIPoint(28.0f, 28.0f),
"Editor shell asset invalid.",
EditorWindowInternal::kShellTextColor,
16.0f);
drawList.AddText(
UIPoint(28.0f, 54.0f),
editorContext.GetValidationMessage().empty()
? std::string("Unknown validation error.")
: editorContext.GetValidationMessage(),
EditorWindowInternal::kShellMutedTextColor,
12.0f);
}
void EditorWindow::LogFrameInteractionTrace(
EditorContext& editorContext,
const std::vector<UIInputEvent>& frameEvents,
const UIEditorShellInteractionFrame& shellFrame) const {
if (!IsVerboseRuntimeTraceEnabled() ||
(frameEvents.empty() &&
!shellFrame.result.workspaceResult.dockHostResult.layoutChanged &&
!shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) {
return;
}
std::ostringstream frameTrace = {};
frameTrace << "result consumed="
<< (shellFrame.result.consumed ? "true" : "false")
<< " layoutChanged="
<< (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false")
<< " commandExecuted="
<< (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false")
<< " active="
<< GetWorkspaceController().GetWorkspace().activePanelId
<< " message="
<< shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
LogRuntimeTrace("frame", frameTrace.str());
}
EditorWindowFrameTransferRequests EditorWindow::BuildShellTransferRequests(
bool globalTabDragActive,
const UIEditorDockHostInteractionState& dockHostInteractionState,
const UIEditorShellInteractionFrame& shellFrame) const {
EditorWindowFrameTransferRequests transferRequests = {};
POINT screenPoint = {};
const bool hasScreenPoint = GetCursorPos(&screenPoint) != FALSE;
if (!globalTabDragActive &&
!dockHostInteractionState.activeTabDragNodeId.empty() &&
!dockHostInteractionState.activeTabDragPanelId.empty() &&
hasScreenPoint) {
transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{
dockHostInteractionState.activeTabDragNodeId,
dockHostInteractionState.activeTabDragPanelId,
screenPoint,
};
}
if (shellFrame.result.workspaceResult.dockHostResult.detachRequested &&
hasScreenPoint) {
transferRequests.detachPanel = EditorWindowPanelTransferRequest{
shellFrame.result.workspaceResult.dockHostResult.detachedNodeId,
shellFrame.result.workspaceResult.dockHostResult.detachedPanelId,
screenPoint,
};
}
return transferRequests;
}
std::string EditorWindow::BuildCaptureStatusText() const {
if (m_state->render.autoScreenshot.HasPendingCapture()) {
return "Shot pending...";
}
if (!m_state->render.autoScreenshot.GetLastCaptureError().empty()) {
return TruncateText(m_state->render.autoScreenshot.GetLastCaptureError(), 38u);
}
if (!m_state->render.autoScreenshot.GetLastCaptureSummary().empty()) {
return TruncateText(m_state->render.autoScreenshot.GetLastCaptureSummary(), 38u);
}
return {};
}
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
m_state->input.modifierTracker.SyncFromSystemState();
const std::uint8_t expectedButtons =
ResolveExpectedShellCaptureButtons(m_state->composition.shellRuntime);
if (expectedButtons == 0u ||
HasPendingPointerStateReconciliationEvent(m_state->input.pendingEvents)) {
return;
}
const UIInputModifiers modifiers =
m_state->input.modifierTracker.GetCurrentModifiers();
if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) {
return;
}
QueueSyntheticPointerStateSyncEvent(modifiers);
}
void EditorWindow::ApplyShellRuntimePointerCapture() {
const EditorShellPointerOwner owner =
m_state->composition.shellRuntime.GetPointerOwner();
if (IsShellPointerOwner(owner)) {
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
return;
}
if (IsHostedContentPointerOwner(owner)) {
AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent);
return;
}
if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::Shell)) {
ReleasePointerCapture(EditorWindowPointerCaptureOwner::Shell);
}
if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::HostedContent)) {
ReleasePointerCapture(EditorWindowPointerCaptureOwner::HostedContent);
}
}
std::string EditorWindow::DescribeInputEvents(
const std::vector<UIInputEvent>& events) const {
std::ostringstream stream = {};
stream << "events=[";
for (std::size_t index = 0; index < events.size(); ++index) {
if (index > 0u) {
stream << " | ";
}
const UIInputEvent& event = events[index];
stream << DescribeInputEventType(event)
<< '@'
<< static_cast<int>(event.position.x)
<< ','
<< static_cast<int>(event.position.y);
}
stream << ']';
return stream.str();
}
} // namespace XCEngine::UI::Editor::App