493 lines
15 KiB
C++
493 lines
15 KiB
C++
#include "Windowing/EditorWindowInstance.h"
|
|
|
|
#include "Support/StringEncoding.h"
|
|
#include "Windowing/Content/EditorWindowContentController.h"
|
|
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
|
|
|
|
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
#include <XCEngine/UI/DrawData.h>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
using ::XCEngine::UI::UIColor;
|
|
using ::XCEngine::UI::UIDrawData;
|
|
using ::XCEngine::UI::UIDrawList;
|
|
using ::XCEngine::UI::UIPoint;
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
EditorWindowInstance::EditorWindowInstance(
|
|
std::string windowId,
|
|
std::wstring title,
|
|
EditorWindowCategory category,
|
|
EditorWindowChromePolicy chromePolicy,
|
|
bool primary,
|
|
std::unique_ptr<EditorWindowRuntimeController> runtimeController)
|
|
: m_windowId(std::move(windowId))
|
|
, m_title(std::move(title))
|
|
, m_category(category)
|
|
, m_chromePolicy(chromePolicy)
|
|
, m_primary(primary)
|
|
, m_runtime(std::move(runtimeController)) {
|
|
UpdateCachedTitleText();
|
|
}
|
|
|
|
EditorWindowInstance::~EditorWindowInstance() = default;
|
|
|
|
std::string_view EditorWindowInstance::GetWindowId() const {
|
|
return m_windowId;
|
|
}
|
|
|
|
EditorWindowLifecycleState EditorWindowInstance::GetLifecycleState() const {
|
|
return m_lifecycle;
|
|
}
|
|
|
|
const EditorWindowChromePolicy& EditorWindowInstance::GetChromePolicy() const {
|
|
return m_chromePolicy;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsPrimary() const {
|
|
return m_primary;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsWorkspaceWindow() const {
|
|
return m_category == EditorWindowCategory::Workspace;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsUtilityWindow() const {
|
|
return m_category == EditorWindowCategory::Utility;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsClosing() const {
|
|
return m_lifecycle == EditorWindowLifecycleState::Closing;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsDestroyed() const {
|
|
return m_lifecycle == EditorWindowLifecycleState::Destroyed;
|
|
}
|
|
|
|
bool EditorWindowInstance::HasLiveHostWindow() const {
|
|
return m_nativePeer != nullptr && m_nativePeer->HasLiveHostWindow();
|
|
}
|
|
|
|
const std::wstring& EditorWindowInstance::GetTitle() const {
|
|
return m_title;
|
|
}
|
|
|
|
std::string_view EditorWindowInstance::GetCachedTitleText() const {
|
|
return m_cachedTitleText;
|
|
}
|
|
|
|
const EditorWorkspaceWindowProjection*
|
|
EditorWindowInstance::TryGetWorkspaceProjection() const {
|
|
const EditorWindowWorkspaceBinding* workspaceBinding =
|
|
m_runtime != nullptr ? m_runtime->TryGetWorkspaceBinding() : nullptr;
|
|
return workspaceBinding != nullptr
|
|
? workspaceBinding->TryGetWorkspaceProjection()
|
|
: nullptr;
|
|
}
|
|
|
|
EditorWindowDockHostBinding* EditorWindowInstance::TryGetDockHostBinding() {
|
|
return m_runtime != nullptr ? m_runtime->TryGetDockHostBinding() : nullptr;
|
|
}
|
|
|
|
const EditorWindowDockHostBinding* EditorWindowInstance::TryGetDockHostBinding() const {
|
|
return m_runtime != nullptr ? m_runtime->TryGetDockHostBinding() : nullptr;
|
|
}
|
|
|
|
const EditorWindowInputFeedbackBinding*
|
|
EditorWindowInstance::TryGetInputFeedbackBinding() const {
|
|
return m_runtime != nullptr ? m_runtime->TryGetInputFeedbackBinding() : nullptr;
|
|
}
|
|
|
|
const EditorWindowTitleBarBinding* EditorWindowInstance::TryGetTitleBarBinding() const {
|
|
return m_runtime != nullptr ? m_runtime->TryGetTitleBarBinding() : nullptr;
|
|
}
|
|
|
|
const UIEditorShellInteractionFrame& EditorWindowInstance::GetShellFrame() const {
|
|
return m_runtime->GetShellFrame();
|
|
}
|
|
|
|
const UIEditorShellInteractionState& EditorWindowInstance::GetShellInteractionState() const {
|
|
return m_runtime->GetShellInteractionState();
|
|
}
|
|
|
|
::XCEngine::UI::UISize EditorWindowInstance::ResolveMinimumOuterSize() const {
|
|
return m_runtime->ResolveMinimumOuterSize();
|
|
}
|
|
|
|
UIEditorTextMeasurer& EditorWindowInstance::GetTextMeasurer() {
|
|
return m_runtime->GetTextMeasurer();
|
|
}
|
|
|
|
const UIEditorTextMeasurer& EditorWindowInstance::GetTextMeasurer() const {
|
|
return m_runtime->GetTextMeasurer();
|
|
}
|
|
|
|
const ::XCEngine::UI::UITextureHandle& EditorWindowInstance::GetTitleBarLogoIcon() const {
|
|
return m_runtime->GetTitleBarLogoIcon();
|
|
}
|
|
|
|
std::string EditorWindowInstance::BuildFrameRateText() const {
|
|
return m_runtime->BuildFrameRateText();
|
|
}
|
|
|
|
::XCEngine::UI::UIPoint EditorWindowInstance::ConvertScreenPixelsToClientDips(
|
|
const EditorWindowScreenPoint& screenPoint) const {
|
|
return m_nativePeer != nullptr
|
|
? m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint)
|
|
: ::XCEngine::UI::UIPoint{};
|
|
}
|
|
|
|
bool EditorWindowInstance::TryResolveDockTabDragHotspot(
|
|
std::string_view nodeId,
|
|
std::string_view panelId,
|
|
const EditorWindowScreenPoint& screenPoint,
|
|
EditorWindowScreenPoint& outHotspot) const {
|
|
const EditorWindowDockHostBinding* dockHostBinding = TryGetDockHostBinding();
|
|
if (dockHostBinding == nullptr || m_nativePeer == nullptr) {
|
|
outHotspot = {};
|
|
return false;
|
|
}
|
|
|
|
const UIPoint clientPointDips = m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint);
|
|
UIPoint hotspotDips = {};
|
|
if (!dockHostBinding->TryResolveDockTabDragHotspot(
|
|
nodeId,
|
|
panelId,
|
|
clientPointDips,
|
|
hotspotDips)) {
|
|
outHotspot = {};
|
|
return false;
|
|
}
|
|
|
|
const float dpiScale = m_nativePeer->CaptureMetrics().dpiScale;
|
|
outHotspot.x = static_cast<std::int32_t>(std::lround(hotspotDips.x * dpiScale));
|
|
outHotspot.y = static_cast<std::int32_t>(std::lround(hotspotDips.y * dpiScale));
|
|
return true;
|
|
}
|
|
|
|
bool EditorWindowInstance::TryResolveDockTabDropTarget(
|
|
const EditorWindowScreenPoint& screenPoint,
|
|
UIEditorDockHostTabDropTarget& outTarget) const {
|
|
const EditorWindowDockHostBinding* dockHostBinding = TryGetDockHostBinding();
|
|
if (dockHostBinding == nullptr || m_nativePeer == nullptr) {
|
|
outTarget = {};
|
|
return false;
|
|
}
|
|
|
|
outTarget = dockHostBinding->ResolveDockTabDropTarget(
|
|
m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint));
|
|
return outTarget.valid;
|
|
}
|
|
|
|
void EditorWindowInstance::InvalidateHostWindow() const {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->InvalidateHostWindow();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::SetPrimary(bool primary) {
|
|
m_primary = primary;
|
|
}
|
|
|
|
void EditorWindowInstance::SetTitle(std::wstring title) {
|
|
m_title = std::move(title);
|
|
UpdateCachedTitleText();
|
|
}
|
|
|
|
void EditorWindowInstance::ApplyHostWindowTitle() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->ApplyHostWindowTitle(m_title);
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::RefreshWorkspaceProjection(
|
|
EditorWorkspaceWindowProjection projection) {
|
|
EditorWindowWorkspaceBinding* workspaceBinding =
|
|
m_runtime != nullptr ? m_runtime->TryGetWorkspaceBinding() : nullptr;
|
|
assert(workspaceBinding != nullptr);
|
|
workspaceBinding->RefreshWorkspaceProjection(std::move(projection));
|
|
}
|
|
|
|
void EditorWindowInstance::ResetInteractionState() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->ResetNativeInteractionState();
|
|
}
|
|
if (m_runtime != nullptr) {
|
|
m_runtime->ResetInteractionState();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::SetDpiScale(float dpiScale) {
|
|
if (m_runtime != nullptr) {
|
|
m_runtime->SetDpiScale(dpiScale);
|
|
}
|
|
}
|
|
|
|
bool EditorWindowInstance::ApplyResize(std::uint32_t width, std::uint32_t height) {
|
|
if (m_runtime == nullptr || !m_runtime->IsReady() || width == 0u || height == 0u) {
|
|
return false;
|
|
}
|
|
return m_runtime->ApplyResize(width, height);
|
|
}
|
|
|
|
void EditorWindowInstance::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->AcquirePointerCapture(owner);
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->ReleasePointerCapture(owner);
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::AttachNativePeer(EditorWindowNativePeer& nativePeer) {
|
|
m_nativePeer = &nativePeer;
|
|
}
|
|
|
|
void EditorWindowInstance::DetachNativePeer(EditorWindowNativePeer& nativePeer) {
|
|
if (m_nativePeer == &nativePeer) {
|
|
m_nativePeer = nullptr;
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::MarkNativeAttached() {
|
|
m_lifecycle = EditorWindowLifecycleState::NativeAttached;
|
|
}
|
|
|
|
void EditorWindowInstance::MarkInitializing() {
|
|
m_lifecycle = EditorWindowLifecycleState::Initializing;
|
|
}
|
|
|
|
void EditorWindowInstance::MarkRunning() {
|
|
m_lifecycle = EditorWindowLifecycleState::Running;
|
|
}
|
|
|
|
void EditorWindowInstance::MarkClosing() {
|
|
m_lifecycle = EditorWindowLifecycleState::Closing;
|
|
}
|
|
|
|
void EditorWindowInstance::MarkDestroyed() {
|
|
m_lifecycle = EditorWindowLifecycleState::Destroyed;
|
|
}
|
|
|
|
bool EditorWindowInstance::IsRenderReady() const {
|
|
return m_runtime != nullptr && m_runtime->IsReady();
|
|
}
|
|
|
|
bool EditorWindowInstance::InitializeRuntime(
|
|
const EditorHostWindowRuntimeInitializationParams& params) {
|
|
if (m_nativePeer == nullptr) {
|
|
AppendUIEditorRuntimeTrace("app", "window initialize skipped: native window is null");
|
|
return false;
|
|
}
|
|
|
|
m_nativePeer->PrepareRuntimeInitialization(*this);
|
|
EditorNativeWindowRuntimeSurface runtimeSurface = {};
|
|
if (!m_nativePeer->CaptureRuntimeSurface(*this, runtimeSurface) ||
|
|
!runtimeSurface.IsValid()) {
|
|
AppendUIEditorRuntimeTrace("app", "window initialize skipped: native surface is invalid");
|
|
return false;
|
|
}
|
|
|
|
m_runtime->SetDpiScale(runtimeSurface.dpiScale);
|
|
|
|
std::ostringstream dpiTrace = {};
|
|
dpiTrace << "initial dpiScale=" << runtimeSurface.dpiScale;
|
|
AppendUIEditorRuntimeTrace("window", dpiTrace.str());
|
|
|
|
MarkInitializing();
|
|
const bool initialized = m_runtime->Initialize(
|
|
Rendering::Host::EditorWindowRenderRuntimeSurface{
|
|
.nativeWindowHandle = runtimeSurface.nativeWindowHandle,
|
|
.widthPixels = runtimeSurface.widthPixels,
|
|
.heightPixels = runtimeSurface.heightPixels,
|
|
},
|
|
params.repoRoot,
|
|
params.captureRoot,
|
|
params.autoCaptureOnStartup);
|
|
if (initialized) {
|
|
MarkRunning();
|
|
} else {
|
|
MarkNativeAttached();
|
|
}
|
|
return initialized;
|
|
}
|
|
|
|
EditorWindowFrameTransferRequests EditorWindowInstance::RenderHostFrame(
|
|
bool globalTabDragActive) {
|
|
if (m_runtime == nullptr || !m_runtime->IsReady() || m_nativePeer == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
EditorNativeWindowFrameSnapshot frameSnapshot = {};
|
|
if (!m_nativePeer->CaptureFrameSnapshot(
|
|
*this,
|
|
m_runtime->GetShellInteractionState(),
|
|
frameSnapshot) ||
|
|
!frameSnapshot.IsValid()) {
|
|
return {};
|
|
}
|
|
|
|
UIDrawData drawData = {};
|
|
UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface");
|
|
backgroundDrawList.AddFilledRect(
|
|
UIRect(0.0f, 0.0f, frameSnapshot.widthDips, frameSnapshot.heightDips),
|
|
kShellSurfaceColor);
|
|
|
|
EditorWindowFrameTransferRequests transferRequests = {};
|
|
if (m_runtime->IsEditorContextValid()) {
|
|
transferRequests =
|
|
RenderRuntimeFrame(globalTabDragActive, frameSnapshot, drawData);
|
|
} else {
|
|
UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid");
|
|
m_runtime->AppendInvalidFrame(invalidDrawList);
|
|
}
|
|
|
|
UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome");
|
|
m_nativePeer->AppendChrome(*this, windowChromeDrawList, frameSnapshot.widthDips);
|
|
|
|
const auto presentResult = m_runtime->Present(drawData);
|
|
if (!presentResult.warning.empty()) {
|
|
AppendUIEditorRuntimeTrace("present", presentResult.warning);
|
|
}
|
|
return transferRequests;
|
|
}
|
|
|
|
void EditorWindowInstance::ValidateHostFrame() const {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->ValidateHostFrame();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::RequestSkipNextSteadyStateFrame() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->RequestSkipNextSteadyStateFrame();
|
|
}
|
|
}
|
|
|
|
bool EditorWindowInstance::ConsumeSkipNextSteadyStateFrame() {
|
|
return m_nativePeer != nullptr && m_nativePeer->ConsumeSkipNextSteadyStateFrame();
|
|
}
|
|
|
|
void EditorWindowInstance::Shutdown() {
|
|
std::ostringstream trace = {};
|
|
trace << "EditorWindowInstance::Shutdown begin windowId='" << GetWindowId()
|
|
<< "' primary=" << (IsPrimary() ? 1 : 0)
|
|
<< " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState())
|
|
<< " runtimeReady=" << (IsRenderReady() ? 1 : 0);
|
|
AppendUIEditorRuntimeTrace("window-close", trace.str());
|
|
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->ShutdownNativeInteraction();
|
|
}
|
|
if (m_runtime != nullptr && m_runtime->IsReady()) {
|
|
m_runtime->Shutdown();
|
|
}
|
|
AppendUIEditorRuntimeTrace(
|
|
"window-close",
|
|
"EditorWindowInstance::Shutdown end windowId='" + std::string(GetWindowId()) + "'");
|
|
}
|
|
|
|
bool EditorWindowInstance::TryGetHostScreenRect(EditorWindowScreenRect& outRect) const {
|
|
outRect = {};
|
|
return m_nativePeer != nullptr && m_nativePeer->TryGetHostScreenRect(outRect);
|
|
}
|
|
|
|
void EditorWindowInstance::SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->SetHostScreenPosition(screenPoint);
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::FocusHostWindow() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->FocusHostWindow();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::PostCloseToHost() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->PostCloseToHost();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::DestroyHostWindow() {
|
|
if (m_nativePeer != nullptr) {
|
|
m_nativePeer->DestroyHostWindow();
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::RequestManualScreenshot(std::string reason) {
|
|
if (m_runtime != nullptr) {
|
|
m_runtime->RequestManualScreenshot(std::move(reason));
|
|
}
|
|
}
|
|
|
|
void EditorWindowInstance::UpdateCachedTitleText() {
|
|
m_cachedTitleText = WideToUtf8(m_title);
|
|
}
|
|
|
|
EditorWindowFrameTransferRequests EditorWindowInstance::RenderRuntimeFrame(
|
|
bool globalTabDragActive,
|
|
const EditorNativeWindowFrameSnapshot& frameSnapshot,
|
|
UIDrawData& drawData) {
|
|
if (m_nativePeer == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
m_runtime->PrepareEditorContext();
|
|
const auto frameContext = m_runtime->BeginFrame();
|
|
if (!frameContext.warning.empty()) {
|
|
AppendUIEditorRuntimeTrace("viewport", frameContext.warning);
|
|
}
|
|
|
|
const EditorWindowFrameTransferRequests transferRequests =
|
|
m_runtime->UpdateAndAppend(
|
|
frameSnapshot.workspaceBounds,
|
|
frameSnapshot.inputEvents,
|
|
frameSnapshot.cursorScreenPoint,
|
|
IsPrimary(),
|
|
globalTabDragActive,
|
|
frameSnapshot.useDetachedTitleBarTabStrip,
|
|
drawData);
|
|
if (frameContext.canRenderViewports) {
|
|
m_runtime->RenderRequestedViewports(frameContext.renderContext);
|
|
}
|
|
|
|
m_nativePeer->ApplyFrameCommands(
|
|
*this,
|
|
EditorNativeWindowFrameCommands{
|
|
.applyShellRuntimePointerCapture = true,
|
|
.applyCurrentCursor = true,
|
|
});
|
|
return transferRequests;
|
|
}
|
|
|
|
std::unique_ptr<EditorWindowInstance> CreateEditorWindowInstance(
|
|
std::string windowId,
|
|
std::wstring title,
|
|
EditorWindowCategory category,
|
|
EditorWindowChromePolicy chromePolicy,
|
|
bool primary,
|
|
std::unique_ptr<EditorWindowRuntimeController> runtimeController) {
|
|
return std::make_unique<EditorWindowInstance>(
|
|
std::move(windowId),
|
|
std::move(title),
|
|
category,
|
|
chromePolicy,
|
|
primary,
|
|
std::move(runtimeController));
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|