417 lines
14 KiB
C++
417 lines
14 KiB
C++
#include "Platform/Win32/EditorWindow.h"
|
|
#include "Platform/Win32/EditorWindowConstants.h"
|
|
#include "Platform/Win32/EditorWindowRuntimeInternal.h"
|
|
#include "Platform/Win32/EditorWindowStyle.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_render.ready || m_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_render.windowRenderLoop.Present(drawData);
|
|
if (!presentResult.warning.empty()) {
|
|
LogRuntimeTrace("present", presentResult.warning);
|
|
}
|
|
|
|
m_render.autoScreenshot.CaptureIfRequested(
|
|
m_render.renderer,
|
|
drawData,
|
|
pixelWidth,
|
|
pixelHeight,
|
|
presentResult.framePresented);
|
|
return transferRequests;
|
|
}
|
|
|
|
EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage(
|
|
EditorContext& editorContext,
|
|
bool globalTabDragActive) {
|
|
if (!m_render.ready || m_window.hwnd == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
PAINTSTRUCT paintStruct = {};
|
|
BeginPaint(m_window.hwnd, &paintStruct);
|
|
const EditorWindowFrameTransferRequests transferRequests =
|
|
RenderFrame(editorContext, globalTabDragActive);
|
|
EndPaint(m_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_input.pendingEvents);
|
|
m_input.pendingEvents.clear();
|
|
if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) {
|
|
LogRuntimeTrace(
|
|
"input",
|
|
DescribeInputEvents(frameEvents) + " | " +
|
|
editorContext.DescribeWorkspaceState(
|
|
m_composition.workspaceController,
|
|
m_composition.shellRuntime.GetShellInteractionState()));
|
|
}
|
|
|
|
const Host::D3D12WindowRenderLoopFrameContext frameContext =
|
|
m_render.windowRenderLoop.BeginFrame();
|
|
if (!frameContext.warning.empty()) {
|
|
LogRuntimeTrace("viewport", frameContext.warning);
|
|
}
|
|
|
|
editorContext.AttachTextMeasurer(m_render.renderer);
|
|
const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip();
|
|
m_composition.shellRuntime.Update(
|
|
editorContext,
|
|
m_composition.workspaceController,
|
|
workspaceBounds,
|
|
frameEvents,
|
|
BuildCaptureStatusText(),
|
|
m_window.primary
|
|
? EditorShellVariant::Primary
|
|
: EditorShellVariant::DetachedWindow,
|
|
useDetachedTitleBarTabStrip,
|
|
useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f);
|
|
const UIEditorShellInteractionFrame& shellFrame =
|
|
m_composition.shellRuntime.GetShellFrame();
|
|
const UIEditorDockHostInteractionState& dockHostInteractionState =
|
|
m_composition.shellRuntime
|
|
.GetShellInteractionState()
|
|
.workspaceInteractionState
|
|
.dockHostInteractionState;
|
|
|
|
LogFrameInteractionTrace(editorContext, frameEvents, shellFrame);
|
|
const EditorWindowFrameTransferRequests transferRequests =
|
|
BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame);
|
|
|
|
ApplyHostCaptureRequests(shellFrame.result);
|
|
for (const WorkspaceTraceEntry& entry : m_composition.shellRuntime.GetTraceEntries()) {
|
|
LogRuntimeTrace(entry.channel, entry.message);
|
|
}
|
|
ApplyHostedContentCaptureRequests();
|
|
ApplyCurrentCursor();
|
|
m_composition.shellRuntime.Append(drawList);
|
|
if (frameContext.canRenderViewports) {
|
|
m_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="
|
|
<< m_composition.workspaceController.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_render.autoScreenshot.HasPendingCapture()) {
|
|
return "Shot pending...";
|
|
}
|
|
|
|
if (!m_render.autoScreenshot.GetLastCaptureError().empty()) {
|
|
return TruncateText(m_render.autoScreenshot.GetLastCaptureError(), 38u);
|
|
}
|
|
|
|
if (!m_render.autoScreenshot.GetLastCaptureSummary().empty()) {
|
|
return TruncateText(m_render.autoScreenshot.GetLastCaptureSummary(), 38u);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
|
|
m_input.modifierTracker.SyncFromSystemState();
|
|
|
|
const std::uint8_t expectedButtons =
|
|
ResolveExpectedShellCaptureButtons(m_composition.shellRuntime);
|
|
if (expectedButtons == 0u ||
|
|
HasPendingPointerStateReconciliationEvent(m_input.pendingEvents)) {
|
|
return;
|
|
}
|
|
|
|
const UIInputModifiers modifiers =
|
|
m_input.modifierTracker.GetCurrentModifiers();
|
|
if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) {
|
|
return;
|
|
}
|
|
|
|
QueueSyntheticPointerStateSyncEvent(modifiers);
|
|
}
|
|
|
|
void EditorWindow::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) {
|
|
if (result.requestPointerCapture) {
|
|
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
|
|
}
|
|
if (result.releasePointerCapture) {
|
|
ReleasePointerCapture(EditorWindowPointerCaptureOwner::Shell);
|
|
}
|
|
}
|
|
|
|
void EditorWindow::ApplyHostedContentCaptureRequests() {
|
|
if (m_composition.shellRuntime.WantsHostPointerCapture()) {
|
|
AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent);
|
|
}
|
|
|
|
if (m_composition.shellRuntime.WantsHostPointerRelease() &&
|
|
!m_composition.shellRuntime.HasShellInteractiveCapture()) {
|
|
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
|