1188 lines
39 KiB
C++
1188 lines
39 KiB
C++
#include "Application.h"
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
#include <XCEditor/Foundation/UIEditorTheme.h>
|
|
|
|
#include <XCEngine/Input/InputTypes.h>
|
|
#include <XCEngine/UI/DrawData.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include <shellscalingapi.h>
|
|
|
|
#ifndef XCUIEDITOR_REPO_ROOT
|
|
#define XCUIEDITOR_REPO_ROOT "."
|
|
#endif
|
|
|
|
namespace XCEngine::UI::Editor {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::Input::KeyCode;
|
|
using ::XCEngine::UI::UIColor;
|
|
using ::XCEngine::UI::UIDrawData;
|
|
using ::XCEngine::UI::UIDrawList;
|
|
using ::XCEngine::UI::UIInputEvent;
|
|
using ::XCEngine::UI::UIInputEventType;
|
|
using ::XCEngine::UI::UIPoint;
|
|
using ::XCEngine::UI::UIPointerButton;
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
|
|
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
|
|
constexpr UINT kDefaultDpi = 96u;
|
|
constexpr float kBaseDpiScale = 96.0f;
|
|
constexpr UINT kDeferredRenderMessage = WM_APP + 1u;
|
|
|
|
bool ResolveVerboseRuntimeTraceEnabled() {
|
|
wchar_t buffer[8] = {};
|
|
const DWORD length = GetEnvironmentVariableW(
|
|
L"XCUIEDITOR_VERBOSE_TRACE",
|
|
buffer,
|
|
static_cast<DWORD>(std::size(buffer)));
|
|
return length > 0u && buffer[0] != L'0';
|
|
}
|
|
|
|
Application* GetApplicationFromWindow(HWND hwnd) {
|
|
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
}
|
|
|
|
UINT QuerySystemDpi() {
|
|
HDC screenDc = GetDC(nullptr);
|
|
if (screenDc == nullptr) {
|
|
return kDefaultDpi;
|
|
}
|
|
|
|
const int dpiX = GetDeviceCaps(screenDc, LOGPIXELSX);
|
|
ReleaseDC(nullptr, screenDc);
|
|
return dpiX > 0 ? static_cast<UINT>(dpiX) : kDefaultDpi;
|
|
}
|
|
|
|
UINT QueryWindowDpi(HWND hwnd) {
|
|
if (hwnd != nullptr) {
|
|
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
|
if (user32 != nullptr) {
|
|
using GetDpiForWindowFn = UINT(WINAPI*)(HWND);
|
|
const auto getDpiForWindow =
|
|
reinterpret_cast<GetDpiForWindowFn>(GetProcAddress(user32, "GetDpiForWindow"));
|
|
if (getDpiForWindow != nullptr) {
|
|
const UINT dpi = getDpiForWindow(hwnd);
|
|
if (dpi != 0u) {
|
|
return dpi;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return QuerySystemDpi();
|
|
}
|
|
|
|
void EnableDpiAwareness() {
|
|
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
|
if (user32 != nullptr) {
|
|
using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT);
|
|
const auto setProcessDpiAwarenessContext =
|
|
reinterpret_cast<SetProcessDpiAwarenessContextFn>(
|
|
GetProcAddress(user32, "SetProcessDpiAwarenessContext"));
|
|
if (setProcessDpiAwarenessContext != nullptr) {
|
|
if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
|
|
return;
|
|
}
|
|
if (GetLastError() == ERROR_ACCESS_DENIED) {
|
|
return;
|
|
}
|
|
if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
|
|
return;
|
|
}
|
|
if (GetLastError() == ERROR_ACCESS_DENIED) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const HMODULE shcore = LoadLibraryW(L"shcore.dll");
|
|
if (shcore != nullptr) {
|
|
using SetProcessDpiAwarenessFn = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS);
|
|
const auto setProcessDpiAwareness =
|
|
reinterpret_cast<SetProcessDpiAwarenessFn>(
|
|
GetProcAddress(shcore, "SetProcessDpiAwareness"));
|
|
if (setProcessDpiAwareness != nullptr) {
|
|
const HRESULT hr = setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
FreeLibrary(shcore);
|
|
if (SUCCEEDED(hr) || hr == E_ACCESSDENIED) {
|
|
return;
|
|
}
|
|
} else {
|
|
FreeLibrary(shcore);
|
|
}
|
|
}
|
|
|
|
if (user32 != nullptr) {
|
|
using SetProcessDPIAwareFn = BOOL(WINAPI*)();
|
|
const auto setProcessDPIAware =
|
|
reinterpret_cast<SetProcessDPIAwareFn>(GetProcAddress(user32, "SetProcessDPIAware"));
|
|
if (setProcessDPIAware != nullptr) {
|
|
setProcessDPIAware();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TryEnableNonClientDpiScaling(HWND hwnd) {
|
|
if (hwnd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
|
if (user32 == nullptr) {
|
|
return;
|
|
}
|
|
|
|
using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND);
|
|
const auto enableNonClientDpiScaling =
|
|
reinterpret_cast<EnableNonClientDpiScalingFn>(
|
|
GetProcAddress(user32, "EnableNonClientDpiScaling"));
|
|
if (enableNonClientDpiScaling != nullptr) {
|
|
enableNonClientDpiScaling(hwnd);
|
|
}
|
|
}
|
|
|
|
std::string TruncateText(const std::string& text, std::size_t maxLength) {
|
|
if (text.size() <= maxLength) {
|
|
return text;
|
|
}
|
|
|
|
if (maxLength <= 3u) {
|
|
return text.substr(0u, maxLength);
|
|
}
|
|
|
|
return text.substr(0u, maxLength - 3u) + "...";
|
|
}
|
|
|
|
bool IsAutoCaptureOnStartupEnabled() {
|
|
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
|
|
if (value == nullptr || value[0] == '\0') {
|
|
return false;
|
|
}
|
|
|
|
std::string normalized = value;
|
|
std::transform(
|
|
normalized.begin(),
|
|
normalized.end(),
|
|
normalized.begin(),
|
|
[](unsigned char character) {
|
|
return static_cast<char>(std::tolower(character));
|
|
});
|
|
return normalized != "0" &&
|
|
normalized != "false" &&
|
|
normalized != "off" &&
|
|
normalized != "no";
|
|
}
|
|
|
|
std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) {
|
|
switch (wParam) {
|
|
case 'A': return static_cast<std::int32_t>(KeyCode::A);
|
|
case 'B': return static_cast<std::int32_t>(KeyCode::B);
|
|
case 'C': return static_cast<std::int32_t>(KeyCode::C);
|
|
case 'D': return static_cast<std::int32_t>(KeyCode::D);
|
|
case 'E': return static_cast<std::int32_t>(KeyCode::E);
|
|
case 'F': return static_cast<std::int32_t>(KeyCode::F);
|
|
case 'G': return static_cast<std::int32_t>(KeyCode::G);
|
|
case 'H': return static_cast<std::int32_t>(KeyCode::H);
|
|
case 'I': return static_cast<std::int32_t>(KeyCode::I);
|
|
case 'J': return static_cast<std::int32_t>(KeyCode::J);
|
|
case 'K': return static_cast<std::int32_t>(KeyCode::K);
|
|
case 'L': return static_cast<std::int32_t>(KeyCode::L);
|
|
case 'M': return static_cast<std::int32_t>(KeyCode::M);
|
|
case 'N': return static_cast<std::int32_t>(KeyCode::N);
|
|
case 'O': return static_cast<std::int32_t>(KeyCode::O);
|
|
case 'P': return static_cast<std::int32_t>(KeyCode::P);
|
|
case 'Q': return static_cast<std::int32_t>(KeyCode::Q);
|
|
case 'R': return static_cast<std::int32_t>(KeyCode::R);
|
|
case 'S': return static_cast<std::int32_t>(KeyCode::S);
|
|
case 'T': return static_cast<std::int32_t>(KeyCode::T);
|
|
case 'U': return static_cast<std::int32_t>(KeyCode::U);
|
|
case 'V': return static_cast<std::int32_t>(KeyCode::V);
|
|
case 'W': return static_cast<std::int32_t>(KeyCode::W);
|
|
case 'X': return static_cast<std::int32_t>(KeyCode::X);
|
|
case 'Y': return static_cast<std::int32_t>(KeyCode::Y);
|
|
case 'Z': return static_cast<std::int32_t>(KeyCode::Z);
|
|
case '0': return static_cast<std::int32_t>(KeyCode::Zero);
|
|
case '1': return static_cast<std::int32_t>(KeyCode::One);
|
|
case '2': return static_cast<std::int32_t>(KeyCode::Two);
|
|
case '3': return static_cast<std::int32_t>(KeyCode::Three);
|
|
case '4': return static_cast<std::int32_t>(KeyCode::Four);
|
|
case '5': return static_cast<std::int32_t>(KeyCode::Five);
|
|
case '6': return static_cast<std::int32_t>(KeyCode::Six);
|
|
case '7': return static_cast<std::int32_t>(KeyCode::Seven);
|
|
case '8': return static_cast<std::int32_t>(KeyCode::Eight);
|
|
case '9': return static_cast<std::int32_t>(KeyCode::Nine);
|
|
case VK_SPACE: return static_cast<std::int32_t>(KeyCode::Space);
|
|
case VK_TAB: return static_cast<std::int32_t>(KeyCode::Tab);
|
|
case VK_RETURN: return static_cast<std::int32_t>(KeyCode::Enter);
|
|
case VK_ESCAPE: return static_cast<std::int32_t>(KeyCode::Escape);
|
|
case VK_SHIFT: return static_cast<std::int32_t>(KeyCode::LeftShift);
|
|
case VK_CONTROL: return static_cast<std::int32_t>(KeyCode::LeftCtrl);
|
|
case VK_MENU: return static_cast<std::int32_t>(KeyCode::LeftAlt);
|
|
case VK_UP: return static_cast<std::int32_t>(KeyCode::Up);
|
|
case VK_DOWN: return static_cast<std::int32_t>(KeyCode::Down);
|
|
case VK_LEFT: return static_cast<std::int32_t>(KeyCode::Left);
|
|
case VK_RIGHT: return static_cast<std::int32_t>(KeyCode::Right);
|
|
case VK_HOME: return static_cast<std::int32_t>(KeyCode::Home);
|
|
case VK_END: return static_cast<std::int32_t>(KeyCode::End);
|
|
case VK_PRIOR: return static_cast<std::int32_t>(KeyCode::PageUp);
|
|
case VK_NEXT: return static_cast<std::int32_t>(KeyCode::PageDown);
|
|
case VK_DELETE: return static_cast<std::int32_t>(KeyCode::Delete);
|
|
case VK_BACK: return static_cast<std::int32_t>(KeyCode::Backspace);
|
|
case VK_F1: return static_cast<std::int32_t>(KeyCode::F1);
|
|
case VK_F2: return static_cast<std::int32_t>(KeyCode::F2);
|
|
case VK_F3: return static_cast<std::int32_t>(KeyCode::F3);
|
|
case VK_F4: return static_cast<std::int32_t>(KeyCode::F4);
|
|
case VK_F5: return static_cast<std::int32_t>(KeyCode::F5);
|
|
case VK_F6: return static_cast<std::int32_t>(KeyCode::F6);
|
|
case VK_F7: return static_cast<std::int32_t>(KeyCode::F7);
|
|
case VK_F8: return static_cast<std::int32_t>(KeyCode::F8);
|
|
case VK_F9: return static_cast<std::int32_t>(KeyCode::F9);
|
|
case VK_F10: return static_cast<std::int32_t>(KeyCode::F10);
|
|
case VK_F11: return static_cast<std::int32_t>(KeyCode::F11);
|
|
case VK_F12: return static_cast<std::int32_t>(KeyCode::F12);
|
|
default: return static_cast<std::int32_t>(KeyCode::None);
|
|
}
|
|
}
|
|
|
|
bool IsRepeatKeyMessage(LPARAM lParam) {
|
|
return (static_cast<unsigned long>(lParam) & (1ul << 30)) != 0ul;
|
|
}
|
|
|
|
std::filesystem::path GetExecutableDirectory() {
|
|
std::vector<wchar_t> buffer(MAX_PATH);
|
|
while (true) {
|
|
const DWORD copied = ::GetModuleFileNameW(
|
|
nullptr,
|
|
buffer.data(),
|
|
static_cast<DWORD>(buffer.size()));
|
|
if (copied == 0u) {
|
|
return std::filesystem::current_path().lexically_normal();
|
|
}
|
|
|
|
if (copied < buffer.size() - 1u) {
|
|
return std::filesystem::path(std::wstring(buffer.data(), copied))
|
|
.parent_path()
|
|
.lexically_normal();
|
|
}
|
|
|
|
buffer.resize(buffer.size() * 2u);
|
|
}
|
|
}
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
std::string DescribeProjectPanelEvent(const App::ProductProjectPanel::Event& event) {
|
|
std::ostringstream stream = {};
|
|
switch (event.kind) {
|
|
case App::ProductProjectPanel::EventKind::AssetSelected:
|
|
stream << "AssetSelected";
|
|
break;
|
|
case App::ProductProjectPanel::EventKind::AssetSelectionCleared:
|
|
stream << "AssetSelectionCleared";
|
|
break;
|
|
case App::ProductProjectPanel::EventKind::FolderNavigated:
|
|
stream << "FolderNavigated";
|
|
break;
|
|
case App::ProductProjectPanel::EventKind::AssetOpened:
|
|
stream << "AssetOpened";
|
|
break;
|
|
case App::ProductProjectPanel::EventKind::ContextMenuRequested:
|
|
stream << "ContextMenuRequested";
|
|
break;
|
|
case App::ProductProjectPanel::EventKind::None:
|
|
default:
|
|
stream << "None";
|
|
break;
|
|
}
|
|
|
|
stream << " source=";
|
|
switch (event.source) {
|
|
case App::ProductProjectPanel::EventSource::Tree:
|
|
stream << "Tree";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::Breadcrumb:
|
|
stream << "Breadcrumb";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::GridPrimary:
|
|
stream << "GridPrimary";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::GridDoubleClick:
|
|
stream << "GridDoubleClick";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::GridSecondary:
|
|
stream << "GridSecondary";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::Background:
|
|
stream << "Background";
|
|
break;
|
|
case App::ProductProjectPanel::EventSource::None:
|
|
default:
|
|
stream << "None";
|
|
break;
|
|
}
|
|
|
|
if (!event.itemId.empty()) {
|
|
stream << " item=" << event.itemId;
|
|
}
|
|
if (!event.displayName.empty()) {
|
|
stream << " label=" << event.displayName;
|
|
}
|
|
return stream.str();
|
|
}
|
|
|
|
std::string DescribeHierarchyPanelEvent(const App::ProductHierarchyPanel::Event& event) {
|
|
std::ostringstream stream = {};
|
|
switch (event.kind) {
|
|
case App::ProductHierarchyPanel::EventKind::SelectionChanged:
|
|
stream << "SelectionChanged";
|
|
break;
|
|
case App::ProductHierarchyPanel::EventKind::Reparented:
|
|
stream << "Reparented";
|
|
break;
|
|
case App::ProductHierarchyPanel::EventKind::MovedToRoot:
|
|
stream << "MovedToRoot";
|
|
break;
|
|
case App::ProductHierarchyPanel::EventKind::RenameRequested:
|
|
stream << "RenameRequested";
|
|
break;
|
|
case App::ProductHierarchyPanel::EventKind::None:
|
|
default:
|
|
stream << "None";
|
|
break;
|
|
}
|
|
|
|
if (!event.itemId.empty()) {
|
|
stream << " item=" << event.itemId;
|
|
}
|
|
if (!event.targetItemId.empty()) {
|
|
stream << " target=" << event.targetItemId;
|
|
}
|
|
if (!event.label.empty()) {
|
|
stream << " label=" << event.label;
|
|
}
|
|
return stream.str();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int Application::Run(HINSTANCE hInstance, int nCmdShow) {
|
|
if (!Initialize(hInstance, nCmdShow)) {
|
|
Shutdown();
|
|
return 1;
|
|
}
|
|
|
|
MSG message = {};
|
|
while (message.message != WM_QUIT) {
|
|
if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) {
|
|
TranslateMessage(&message);
|
|
DispatchMessageW(&message);
|
|
continue;
|
|
}
|
|
|
|
RenderFrame();
|
|
}
|
|
|
|
Shutdown();
|
|
return static_cast<int>(message.wParam);
|
|
}
|
|
|
|
bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
|
m_hInstance = hInstance;
|
|
EnableDpiAwareness();
|
|
const std::filesystem::path repoRoot = ResolveRepoRootPath();
|
|
const std::filesystem::path logRoot =
|
|
GetExecutableDirectory() / "logs";
|
|
InitializeUIEditorRuntimeTrace(logRoot);
|
|
SetUnhandledExceptionFilter(&Application::HandleUnhandledException);
|
|
LogRuntimeTrace("app", "initialize begin");
|
|
if (!m_editorContext.Initialize(repoRoot)) {
|
|
LogRuntimeTrace(
|
|
"app",
|
|
"shell asset validation failed: " + m_editorContext.GetValidationMessage());
|
|
return false;
|
|
}
|
|
|
|
WNDCLASSEXW windowClass = {};
|
|
windowClass.cbSize = sizeof(windowClass);
|
|
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
|
windowClass.lpfnWndProc = &Application::WndProc;
|
|
windowClass.hInstance = hInstance;
|
|
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
windowClass.lpszClassName = kWindowClassName;
|
|
m_windowClassAtom = RegisterClassExW(&windowClass);
|
|
if (m_windowClassAtom == 0) {
|
|
LogRuntimeTrace("app", "window class registration failed");
|
|
return false;
|
|
}
|
|
|
|
m_hwnd = CreateWindowExW(
|
|
0,
|
|
kWindowClassName,
|
|
kWindowTitle,
|
|
WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
1540,
|
|
940,
|
|
nullptr,
|
|
nullptr,
|
|
hInstance,
|
|
this);
|
|
if (m_hwnd == nullptr) {
|
|
LogRuntimeTrace("app", "window creation failed");
|
|
return false;
|
|
}
|
|
m_windowDpi = QueryWindowDpi(m_hwnd);
|
|
m_dpiScale = GetDpiScale();
|
|
m_renderer.SetDpiScale(m_dpiScale);
|
|
m_editorContext.SetExitRequestHandler([this]() {
|
|
if (m_hwnd != nullptr) {
|
|
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
|
}
|
|
});
|
|
|
|
std::ostringstream dpiTrace = {};
|
|
dpiTrace << "initial dpi=" << m_windowDpi << " scale=" << m_dpiScale;
|
|
LogRuntimeTrace("window", dpiTrace.str());
|
|
|
|
if (!m_renderer.Initialize(m_hwnd)) {
|
|
LogRuntimeTrace("app", "renderer initialization failed");
|
|
return false;
|
|
}
|
|
RECT clientRect = {};
|
|
GetClientRect(m_hwnd, &clientRect);
|
|
const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L);
|
|
const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L);
|
|
if (!m_windowRenderer.Initialize(m_hwnd, clientWidth, clientHeight)) {
|
|
LogRuntimeTrace("app", "d3d12 window renderer initialization failed");
|
|
return false;
|
|
}
|
|
const bool hasD3D12WindowInterop = m_renderer.AttachWindowRenderer(m_windowRenderer);
|
|
if (!hasD3D12WindowInterop) {
|
|
LogRuntimeTrace(
|
|
"app",
|
|
"native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
|
|
m_renderer.GetLastRenderError());
|
|
}
|
|
m_editorContext.AttachTextMeasurer(m_renderer);
|
|
m_editorWorkspace.Initialize(repoRoot, m_renderer);
|
|
m_editorWorkspace.AttachViewportWindowRenderer(m_windowRenderer);
|
|
m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasD3D12WindowInterop);
|
|
if (!m_editorWorkspace.GetBuiltInIconError().empty()) {
|
|
LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError());
|
|
}
|
|
LogRuntimeTrace(
|
|
"app",
|
|
"workspace initialized: " +
|
|
m_editorContext.DescribeWorkspaceState(m_editorWorkspace.GetShellInteractionState()));
|
|
|
|
ShowWindow(m_hwnd, nCmdShow);
|
|
UpdateWindow(m_hwnd);
|
|
|
|
m_autoScreenshot.Initialize(m_editorContext.GetShellAsset().captureRootPath);
|
|
if (IsAutoCaptureOnStartupEnabled()) {
|
|
m_autoScreenshot.RequestCapture("startup");
|
|
m_editorContext.SetStatus("Capture", "Startup capture requested.");
|
|
}
|
|
LogRuntimeTrace("app", "initialize completed");
|
|
return true;
|
|
}
|
|
|
|
void Application::Shutdown() {
|
|
LogRuntimeTrace("app", "shutdown begin");
|
|
if (GetCapture() == m_hwnd) {
|
|
ReleaseCapture();
|
|
}
|
|
|
|
m_autoScreenshot.Shutdown();
|
|
m_editorWorkspace.Shutdown();
|
|
m_windowRenderer.Shutdown();
|
|
m_renderer.Shutdown();
|
|
|
|
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
|
DestroyWindow(m_hwnd);
|
|
}
|
|
m_hwnd = nullptr;
|
|
|
|
if (m_windowClassAtom != 0 && m_hInstance != nullptr) {
|
|
UnregisterClassW(kWindowClassName, m_hInstance);
|
|
m_windowClassAtom = 0;
|
|
}
|
|
|
|
LogRuntimeTrace("app", "shutdown end");
|
|
ShutdownUIEditorRuntimeTrace();
|
|
}
|
|
|
|
void Application::RenderFrame() {
|
|
if (m_hwnd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ApplyPendingWindowResize();
|
|
|
|
RECT clientRect = {};
|
|
GetClientRect(m_hwnd, &clientRect);
|
|
const unsigned int pixelWidth =
|
|
static_cast<unsigned int>((std::max)(clientRect.right - clientRect.left, 1L));
|
|
const unsigned int pixelHeight =
|
|
static_cast<unsigned int>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
|
const float width = PixelsToDips(static_cast<float>(pixelWidth));
|
|
const float height = PixelsToDips(static_cast<float>(pixelHeight));
|
|
|
|
UIDrawData drawData = {};
|
|
UIDrawList& drawList = drawData.EmplaceDrawList("XCEditorShell");
|
|
drawList.AddFilledRect(
|
|
UIRect(0.0f, 0.0f, width, height),
|
|
UIColor(0.10f, 0.10f, 0.10f, 1.0f));
|
|
|
|
if (m_editorContext.IsValid()) {
|
|
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
|
|
m_pendingInputEvents.clear();
|
|
if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) {
|
|
LogRuntimeTrace(
|
|
"input",
|
|
DescribeInputEvents(frameEvents) + " | " +
|
|
m_editorContext.DescribeWorkspaceState(
|
|
m_editorWorkspace.GetShellInteractionState()));
|
|
}
|
|
|
|
const bool canUseWindowRenderer = m_renderer.HasAttachedWindowRenderer();
|
|
const bool d3d12FrameBegun =
|
|
canUseWindowRenderer && m_windowRenderer.BeginFrame();
|
|
if (canUseWindowRenderer && !d3d12FrameBegun) {
|
|
LogRuntimeTrace("viewport", "d3d12 frame begin failed");
|
|
}
|
|
|
|
m_editorWorkspace.Update(
|
|
m_editorContext,
|
|
UIRect(0.0f, 0.0f, width, height),
|
|
frameEvents,
|
|
BuildCaptureStatusText());
|
|
const UIEditorShellInteractionFrame& shellFrame =
|
|
m_editorWorkspace.GetShellFrame();
|
|
if (IsVerboseRuntimeTraceEnabled() &&
|
|
(!frameEvents.empty() ||
|
|
shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
|
|
shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) {
|
|
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_editorContext.GetWorkspaceController().GetWorkspace().activePanelId
|
|
<< " message="
|
|
<< shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
|
|
LogRuntimeTrace(
|
|
"frame",
|
|
frameTrace.str());
|
|
}
|
|
ApplyHostCaptureRequests(shellFrame.result);
|
|
for (const App::ProductEditorWorkspaceTraceEntry& entry : m_editorWorkspace.GetTraceEntries()) {
|
|
LogRuntimeTrace(entry.channel, entry.message);
|
|
}
|
|
ApplyHostedContentCaptureRequests();
|
|
ApplyCurrentCursor();
|
|
m_editorWorkspace.Append(drawList);
|
|
if (d3d12FrameBegun) {
|
|
m_editorWorkspace.RenderRequestedViewports(m_windowRenderer.GetRenderContext());
|
|
}
|
|
} else {
|
|
drawList.AddText(
|
|
UIPoint(28.0f, 28.0f),
|
|
"Editor shell asset invalid.",
|
|
UIColor(0.92f, 0.92f, 0.92f, 1.0f),
|
|
16.0f);
|
|
drawList.AddText(
|
|
UIPoint(28.0f, 54.0f),
|
|
m_editorContext.GetValidationMessage().empty()
|
|
? std::string("Unknown validation error.")
|
|
: m_editorContext.GetValidationMessage(),
|
|
UIColor(0.72f, 0.72f, 0.72f, 1.0f),
|
|
12.0f);
|
|
}
|
|
|
|
bool framePresented = false;
|
|
if (m_renderer.HasAttachedWindowRenderer()) {
|
|
framePresented = m_renderer.RenderToWindowRenderer(drawData);
|
|
if (!framePresented) {
|
|
LogRuntimeTrace(
|
|
"present",
|
|
"d3d12 window composition failed, falling back to hwnd renderer: " +
|
|
m_renderer.GetLastRenderError());
|
|
}
|
|
}
|
|
|
|
if (!framePresented) {
|
|
framePresented = m_renderer.Render(drawData);
|
|
}
|
|
m_autoScreenshot.CaptureIfRequested(
|
|
m_renderer,
|
|
drawData,
|
|
pixelWidth,
|
|
pixelHeight,
|
|
framePresented);
|
|
}
|
|
|
|
float Application::GetDpiScale() const {
|
|
const UINT dpi = m_windowDpi == 0u ? kDefaultDpi : m_windowDpi;
|
|
return static_cast<float>(dpi) / kBaseDpiScale;
|
|
}
|
|
|
|
float Application::PixelsToDips(float pixels) const {
|
|
const float dpiScale = GetDpiScale();
|
|
return dpiScale > 0.0f ? pixels / dpiScale : pixels;
|
|
}
|
|
|
|
bool Application::IsPointerInsideClientArea() const {
|
|
if (m_hwnd == nullptr || !IsWindow(m_hwnd)) {
|
|
return false;
|
|
}
|
|
|
|
POINT screenPoint = {};
|
|
if (!GetCursorPos(&screenPoint)) {
|
|
return false;
|
|
}
|
|
|
|
const LPARAM pointParam = MAKELPARAM(
|
|
static_cast<SHORT>(screenPoint.x),
|
|
static_cast<SHORT>(screenPoint.y));
|
|
return SendMessageW(m_hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT;
|
|
}
|
|
|
|
LPCWSTR Application::ResolveCurrentCursorResource() const {
|
|
switch (m_editorWorkspace.GetHostedContentCursorKind()) {
|
|
case App::ProductProjectPanel::CursorKind::ResizeEW:
|
|
return IDC_SIZEWE;
|
|
case App::ProductProjectPanel::CursorKind::Arrow:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (m_editorWorkspace.GetDockCursorKind()) {
|
|
case Widgets::UIEditorDockHostCursorKind::ResizeEW:
|
|
return IDC_SIZEWE;
|
|
case Widgets::UIEditorDockHostCursorKind::ResizeNS:
|
|
return IDC_SIZENS;
|
|
case Widgets::UIEditorDockHostCursorKind::Arrow:
|
|
default:
|
|
return IDC_ARROW;
|
|
}
|
|
}
|
|
|
|
bool Application::ApplyCurrentCursor() const {
|
|
if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) {
|
|
return false;
|
|
}
|
|
|
|
const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource());
|
|
if (cursor == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
SetCursor(cursor);
|
|
return true;
|
|
}
|
|
|
|
UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const {
|
|
return UIPoint(
|
|
PixelsToDips(static_cast<float>(x)),
|
|
PixelsToDips(static_cast<float>(y)));
|
|
}
|
|
|
|
std::string Application::BuildCaptureStatusText() const {
|
|
if (m_autoScreenshot.HasPendingCapture()) {
|
|
return "Shot pending...";
|
|
}
|
|
|
|
if (!m_autoScreenshot.GetLastCaptureError().empty()) {
|
|
return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u);
|
|
}
|
|
|
|
if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
|
|
return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void Application::LogRuntimeTrace(
|
|
std::string_view channel,
|
|
std::string_view message) const {
|
|
AppendUIEditorRuntimeTrace(channel, message);
|
|
}
|
|
|
|
bool Application::IsVerboseRuntimeTraceEnabled() {
|
|
static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled();
|
|
return s_enabled;
|
|
}
|
|
|
|
std::string Application::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();
|
|
}
|
|
|
|
void Application::OnResize() {
|
|
QueueCurrentClientResize();
|
|
}
|
|
|
|
void Application::OnEnterSizeMove() {
|
|
m_inInteractiveResize = true;
|
|
}
|
|
|
|
void Application::OnExitSizeMove() {
|
|
m_inInteractiveResize = false;
|
|
QueueCurrentClientResize();
|
|
}
|
|
|
|
void Application::QueueWindowResize(UINT width, UINT height) {
|
|
if (width == 0u || height == 0u) {
|
|
return;
|
|
}
|
|
|
|
m_pendingWindowResizeWidth = width;
|
|
m_pendingWindowResizeHeight = height;
|
|
m_hasPendingWindowResize = true;
|
|
}
|
|
|
|
void Application::QueueCurrentClientResize() {
|
|
UINT width = 0u;
|
|
UINT height = 0u;
|
|
if (!QueryCurrentClientPixelSize(width, height)) {
|
|
return;
|
|
}
|
|
|
|
QueueWindowResize(width, height);
|
|
}
|
|
|
|
bool Application::ApplyPendingWindowResize() {
|
|
if (!m_hasPendingWindowResize) {
|
|
return true;
|
|
}
|
|
|
|
const UINT width = m_pendingWindowResizeWidth;
|
|
const UINT height = m_pendingWindowResizeHeight;
|
|
m_hasPendingWindowResize = false;
|
|
if (width == 0u || height == 0u) {
|
|
return false;
|
|
}
|
|
|
|
m_renderer.Resize(width, height);
|
|
m_renderer.DetachWindowRenderer();
|
|
const bool resizedWindowRenderer =
|
|
m_windowRenderer.Resize(static_cast<int>(width), static_cast<int>(height));
|
|
const bool hasD3D12WindowInterop = resizedWindowRenderer &&
|
|
m_renderer.AttachWindowRenderer(m_windowRenderer);
|
|
const bool hasHealthyD3D12WindowInterop =
|
|
resizedWindowRenderer &&
|
|
hasD3D12WindowInterop;
|
|
m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasHealthyD3D12WindowInterop);
|
|
|
|
if (!resizedWindowRenderer || !m_windowRenderer.GetLastError().empty()) {
|
|
LogRuntimeTrace(
|
|
"present",
|
|
"window renderer resize warning: " + m_windowRenderer.GetLastError());
|
|
}
|
|
|
|
if (!hasD3D12WindowInterop) {
|
|
LogRuntimeTrace(
|
|
"present",
|
|
"failed to rebuild d3d12 window interop after resize: " +
|
|
m_renderer.GetLastRenderError());
|
|
}
|
|
|
|
return hasHealthyD3D12WindowInterop;
|
|
}
|
|
|
|
void Application::RequestDeferredRenderFrame() {
|
|
if (m_hwnd == nullptr || !IsWindow(m_hwnd) || m_renderFrameQueued) {
|
|
return;
|
|
}
|
|
|
|
m_renderFrameQueued = true;
|
|
PostMessageW(m_hwnd, kDeferredRenderMessage, 0, 0);
|
|
}
|
|
|
|
bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const {
|
|
outWidth = 0u;
|
|
outHeight = 0u;
|
|
if (m_hwnd == nullptr || !IsWindow(m_hwnd)) {
|
|
return false;
|
|
}
|
|
|
|
RECT clientRect = {};
|
|
if (!GetClientRect(m_hwnd, &clientRect)) {
|
|
return false;
|
|
}
|
|
|
|
const LONG width = clientRect.right - clientRect.left;
|
|
const LONG height = clientRect.bottom - clientRect.top;
|
|
if (width <= 0 || height <= 0) {
|
|
return false;
|
|
}
|
|
|
|
outWidth = static_cast<UINT>(width);
|
|
outHeight = static_cast<UINT>(height);
|
|
return true;
|
|
}
|
|
|
|
void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
|
|
m_windowDpi = dpi == 0u ? kDefaultDpi : dpi;
|
|
m_dpiScale = GetDpiScale();
|
|
m_renderer.SetDpiScale(m_dpiScale);
|
|
if (m_hwnd != nullptr) {
|
|
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
|
|
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
|
|
SetWindowPos(
|
|
m_hwnd,
|
|
nullptr,
|
|
suggestedRect.left,
|
|
suggestedRect.top,
|
|
windowWidth,
|
|
windowHeight,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
QueueCurrentClientResize();
|
|
RequestDeferredRenderFrame();
|
|
}
|
|
|
|
std::ostringstream trace = {};
|
|
trace << "dpi changed to " << m_windowDpi << " scale=" << m_dpiScale;
|
|
LogRuntimeTrace("window", trace.str());
|
|
}
|
|
|
|
void Application::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) {
|
|
if (result.requestPointerCapture && GetCapture() != m_hwnd) {
|
|
SetCapture(m_hwnd);
|
|
}
|
|
if (result.releasePointerCapture && GetCapture() == m_hwnd) {
|
|
ReleaseCapture();
|
|
}
|
|
}
|
|
|
|
void Application::ApplyHostedContentCaptureRequests() {
|
|
if (m_editorWorkspace.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
|
|
SetCapture(m_hwnd);
|
|
}
|
|
|
|
if (m_editorWorkspace.WantsHostPointerRelease() &&
|
|
GetCapture() == m_hwnd &&
|
|
!m_editorWorkspace.HasShellInteractiveCapture()) {
|
|
ReleaseCapture();
|
|
}
|
|
}
|
|
|
|
bool Application::HasInteractiveCaptureState() const {
|
|
return m_editorWorkspace.HasInteractiveCapture();
|
|
}
|
|
|
|
void Application::QueuePointerEvent(
|
|
UIInputEventType type,
|
|
UIPointerButton button,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
UIInputEvent event = {};
|
|
event.type = type;
|
|
event.pointerButton = button;
|
|
event.position = ConvertClientPixelsToDips(
|
|
GET_X_LPARAM(lParam),
|
|
GET_Y_LPARAM(lParam));
|
|
event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast<std::size_t>(wParam));
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
void Application::QueuePointerLeaveEvent() {
|
|
UIInputEvent event = {};
|
|
event.type = UIInputEventType::PointerLeave;
|
|
if (m_hwnd != nullptr) {
|
|
POINT clientPoint = {};
|
|
GetCursorPos(&clientPoint);
|
|
ScreenToClient(m_hwnd, &clientPoint);
|
|
event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y);
|
|
}
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
void Application::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) {
|
|
if (m_hwnd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
POINT screenPoint = {
|
|
GET_X_LPARAM(lParam),
|
|
GET_Y_LPARAM(lParam)
|
|
};
|
|
ScreenToClient(m_hwnd, &screenPoint);
|
|
|
|
UIInputEvent event = {};
|
|
event.type = UIInputEventType::PointerWheel;
|
|
event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y);
|
|
event.wheelDelta = static_cast<float>(wheelDelta);
|
|
event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast<std::size_t>(wParam));
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
void Application::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) {
|
|
UIInputEvent event = {};
|
|
event.type = type;
|
|
event.keyCode = MapVirtualKeyToUIKeyCode(wParam);
|
|
event.modifiers = m_inputModifierTracker.ApplyKeyMessage(type, wParam, lParam);
|
|
event.repeat = IsRepeatKeyMessage(lParam);
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
void Application::QueueCharacterEvent(WPARAM wParam, LPARAM) {
|
|
UIInputEvent event = {};
|
|
event.type = UIInputEventType::Character;
|
|
event.character = static_cast<std::uint32_t>(wParam);
|
|
event.modifiers = m_inputModifierTracker.GetCurrentModifiers();
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
void Application::QueueWindowFocusEvent(UIInputEventType type) {
|
|
UIInputEvent event = {};
|
|
event.type = type;
|
|
m_pendingInputEvents.push_back(event);
|
|
}
|
|
|
|
std::filesystem::path Application::ResolveRepoRootPath() {
|
|
std::string root = XCUIEDITOR_REPO_ROOT;
|
|
if (root.size() >= 2u && root.front() == '"' && root.back() == '"') {
|
|
root = root.substr(1u, root.size() - 2u);
|
|
}
|
|
return std::filesystem::path(root).lexically_normal();
|
|
}
|
|
|
|
LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo) {
|
|
if (exceptionInfo != nullptr &&
|
|
exceptionInfo->ExceptionRecord != nullptr) {
|
|
AppendUIEditorCrashTrace(
|
|
exceptionInfo->ExceptionRecord->ExceptionCode,
|
|
exceptionInfo->ExceptionRecord->ExceptionAddress);
|
|
} else {
|
|
AppendUIEditorCrashTrace(0u, nullptr);
|
|
}
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
if (message == WM_NCCREATE) {
|
|
TryEnableNonClientDpiScaling(hwnd);
|
|
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
|
auto* application = reinterpret_cast<Application*>(createStruct->lpCreateParams);
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(application));
|
|
return TRUE;
|
|
}
|
|
|
|
Application* application = GetApplicationFromWindow(hwnd);
|
|
switch (message) {
|
|
case WM_SETCURSOR:
|
|
if (application != nullptr &&
|
|
LOWORD(lParam) == HTCLIENT &&
|
|
application->ApplyCurrentCursor()) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
case WM_DPICHANGED:
|
|
if (application != nullptr && lParam != 0) {
|
|
application->OnDpiChanged(
|
|
static_cast<UINT>(LOWORD(wParam)),
|
|
*reinterpret_cast<RECT*>(lParam));
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_ENTERSIZEMOVE:
|
|
if (application != nullptr) {
|
|
application->OnEnterSizeMove();
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_EXITSIZEMOVE:
|
|
if (application != nullptr) {
|
|
application->OnExitSizeMove();
|
|
application->RequestDeferredRenderFrame();
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
if (application != nullptr && wParam != SIZE_MINIMIZED) {
|
|
application->OnResize();
|
|
application->RequestDeferredRenderFrame();
|
|
}
|
|
return 0;
|
|
case kDeferredRenderMessage:
|
|
if (application != nullptr) {
|
|
application->m_renderFrameQueued = false;
|
|
application->RenderFrame();
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
if (application != nullptr) {
|
|
PAINTSTRUCT paintStruct = {};
|
|
BeginPaint(hwnd, &paintStruct);
|
|
application->RenderFrame();
|
|
EndPaint(hwnd, &paintStruct);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
if (application != nullptr) {
|
|
if (!application->m_trackingMouseLeave) {
|
|
TRACKMOUSEEVENT trackMouseEvent = {};
|
|
trackMouseEvent.cbSize = sizeof(trackMouseEvent);
|
|
trackMouseEvent.dwFlags = TME_LEAVE;
|
|
trackMouseEvent.hwndTrack = hwnd;
|
|
if (TrackMouseEvent(&trackMouseEvent)) {
|
|
application->m_trackingMouseLeave = true;
|
|
}
|
|
}
|
|
application->QueuePointerEvent(
|
|
UIInputEventType::PointerMove,
|
|
UIPointerButton::None,
|
|
wParam,
|
|
lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_MOUSELEAVE:
|
|
if (application != nullptr) {
|
|
application->m_trackingMouseLeave = false;
|
|
application->QueuePointerLeaveEvent();
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
if (application != nullptr) {
|
|
SetFocus(hwnd);
|
|
application->QueuePointerEvent(
|
|
UIInputEventType::PointerButtonDown,
|
|
UIPointerButton::Left,
|
|
wParam,
|
|
lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
if (application != nullptr) {
|
|
application->QueuePointerEvent(
|
|
UIInputEventType::PointerButtonUp,
|
|
UIPointerButton::Left,
|
|
wParam,
|
|
lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
if (application != nullptr) {
|
|
application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_SETFOCUS:
|
|
if (application != nullptr) {
|
|
application->m_inputModifierTracker.SyncFromSystemState();
|
|
application->QueueWindowFocusEvent(UIInputEventType::FocusGained);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_KILLFOCUS:
|
|
if (application != nullptr) {
|
|
application->m_inputModifierTracker.Reset();
|
|
application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_CAPTURECHANGED:
|
|
if (application != nullptr &&
|
|
reinterpret_cast<HWND>(lParam) != hwnd &&
|
|
application->HasInteractiveCaptureState()) {
|
|
application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
if (application != nullptr) {
|
|
if (wParam == VK_F12) {
|
|
application->m_autoScreenshot.RequestCapture("manual_f12");
|
|
}
|
|
application->QueueKeyEvent(UIInputEventType::KeyDown, wParam, lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
if (application != nullptr) {
|
|
application->QueueKeyEvent(UIInputEventType::KeyUp, wParam, lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_CHAR:
|
|
if (application != nullptr) {
|
|
application->QueueCharacterEvent(wParam, lParam);
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_DESTROY:
|
|
if (application != nullptr) {
|
|
application->m_hwnd = nullptr;
|
|
}
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DefWindowProcW(hwnd, message, wParam, lParam);
|
|
}
|
|
|
|
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow) {
|
|
Application application;
|
|
return application.Run(hInstance, nCmdShow);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor
|