From 603d003684c131720bcd01b43cb31b01abd3bcf6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 27 Apr 2026 23:18:04 +0800 Subject: [PATCH] Refactor editor host resource boundary --- docs/plan/editor-core-refactor-plan.md | 15 ++- editor/AGENTS.md | 12 +- editor/CMakeLists.txt | 3 +- editor/app/Bootstrap/Application.cpp | 9 +- editor/app/Bootstrap/Application.h | 2 + editor/app/Composition/EditorShellRuntime.cpp | 3 +- editor/app/Composition/EditorShellRuntime.h | 7 ++ .../Interfaces/EditorHostResourceService.h | 45 +++++++ editor/app/Host/Interfaces/HostFwd.h | 6 + .../Resources/Win32EditorResourceService.cpp | 119 ++++++++++++++++++ .../Resources/Win32EditorResourceService.h | 27 ++++ .../Host/Win32/System/Win32StringEncoding.h | 82 ++++++++++++ .../System/Win32SystemInteractionHost.cpp | 2 +- .../Win32/Windowing/EditorWindowSupport.h | 2 +- editor/app/Rendering/Assets/BuiltInIcons.cpp | 63 +++++++--- editor/app/Rendering/Assets/BuiltInIcons.h | 4 +- editor/app/Support/EmbeddedPngLoader.cpp | 65 ---------- editor/app/Support/EmbeddedPngLoader.h | 31 ----- editor/app/Support/ExecutablePath.h | 31 ----- editor/app/Support/StringEncoding.h | 62 ++------- .../Content/EditorWindowContentController.h | 7 ++ ...EditorWorkspaceWindowContentController.cpp | 6 +- editor/app/Windowing/EditorWindowManager.cpp | 4 + editor/app/Windowing/EditorWindowManager.h | 8 ++ .../Runtime/EditorWindowRuntimeController.cpp | 30 ++++- .../Runtime/EditorWindowRuntimeController.h | 12 ++ .../EditorWindowScreenshotController.cpp | 14 ++- .../EditorWindowScreenshotController.h | 4 +- 28 files changed, 447 insertions(+), 228 deletions(-) create mode 100644 editor/app/Host/Interfaces/EditorHostResourceService.h create mode 100644 editor/app/Host/Win32/Resources/Win32EditorResourceService.cpp create mode 100644 editor/app/Host/Win32/Resources/Win32EditorResourceService.h create mode 100644 editor/app/Host/Win32/System/Win32StringEncoding.h delete mode 100644 editor/app/Support/EmbeddedPngLoader.cpp delete mode 100644 editor/app/Support/EmbeddedPngLoader.h delete mode 100644 editor/app/Support/ExecutablePath.h diff --git a/docs/plan/editor-core-refactor-plan.md b/docs/plan/editor-core-refactor-plan.md index e6c3cb23..222060e3 100644 --- a/docs/plan/editor-core-refactor-plan.md +++ b/docs/plan/editor-core-refactor-plan.md @@ -42,6 +42,11 @@ incomplete: explicit module roots instead of a private `editor/app` compatibility root. Concrete host code has converged under `editor/app/Host/Win32` and `editor/app/Host/D3D12`. +- Native editor resources now cross the app-core boundary through a neutral + `EditorHostResourceService` contract under `editor/app/Host/Interfaces`. + `XCEditorCore` no longer consumes `app/Bootstrap` or Win32 resource helper + code to load built-in PNGs, title-bar branding, or executable-relative + capture output paths. - Feature panels no longer use `Composition/EditorContext.h` directly. The app-core and app feature/viewport test targets now exercise `XCEditorCore` outside the executable host. @@ -81,8 +86,8 @@ Completed boundary cuts: `XCEditorCore` and `XCEditor`. App implementation files now include through explicit module roots such as `app/Composition`, `app/Features`, `app/Windowing`, `app/Rendering`, `app/Scene`, `app/Services`, - `app/Support`, `app/Bootstrap`, `app/Host/Interfaces`, `app/Host/Win32`, - and `app/Host/D3D12`. + `app/Support`, `app/Host/Interfaces`, `app/Host/Win32`, and + `app/Host/D3D12`; only executable-host code consumes `app/Bootstrap`. - `editor_app_core_tests` now links `XCEditorCore` directly and uses explicit app module include roots. Its initial suite covers host command routing, project runtime, shell asset validation, project browser model, hierarchy @@ -448,6 +453,12 @@ Completed cuts: - Concrete Win32 and D3D12 host implementations now live under `app/Host/Win32` and `app/Host/D3D12`; `XCEditor` consumes those roots privately, while `XCEditorCore` only sees `app/Host/Interfaces`. +- Native PNG resource lookup and executable-directory discovery now go through + `Host/Interfaces/EditorHostResourceService.h`. The concrete + `Host/Win32/Resources/Win32EditorResourceService.*` owns `EditorResources.h`, + Win32 resource APIs, and the mapping from product resource IDs to built-in + editor icon requests, so `XCEditorCore` no longer needs `app/Bootstrap` as a + private include root for resource loading. ## Phase 6: Documentation Update diff --git a/editor/AGENTS.md b/editor/AGENTS.md index f342d0c1..490a4d08 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -32,7 +32,7 @@ change. - `XCEditorCore` also does not consume the whole `editor/app` root privately. Its implementation include paths enumerate concrete app module roots such as `app/Composition`, `app/Features`, `app/Windowing`, `app/Rendering`, - `app/Scene`, `app/Services`, `app/Support`, and `app/Bootstrap`. + `app/Scene`, `app/Services`, and `app/Support`. - `XCEditor` is built when `XCENGINE_BUILD_XCUI_EDITOR_APP=ON`; that mode requires `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON`. The executable target is named `XCEditor`, but its output name is `XCEngine`. @@ -61,8 +61,8 @@ change. workspace-panel runtime interface, and utility-window runtime/descriptors. - `app/Host/Interfaces/` contains neutral host-facing contracts used across editor core and executable host code: editor window host interfaces, - render-runtime factories, UI texture hosts, viewport render hosts, and system - interaction services. + render-runtime factories, UI texture hosts, viewport render hosts, host + resource services, and system interaction services. - `app/Host/Win32/` owns `HWND`, message dispatch, native pointer capture, borderless chrome, DPI, placement, and Win32 system integration. - `app/Host/D3D12/` owns the concrete D3D12 editor window render runtime, UI @@ -451,6 +451,12 @@ ownership rule. - Concrete host code now lives under `app/Host/Win32` and `app/Host/D3D12`. `XCEditorCore` publicly exposes only `app/Host/Interfaces`, while `XCEditor` consumes the concrete host module roots privately. +- Native PNG resource loading and executable-directory discovery now enter + editor core through `Host/Interfaces/EditorHostResourceService.h`. Concrete + Win32 resource IDs and Win32 resource APIs live under + `app/Host/Win32/Resources`, so `XCEditorCore` no longer consumes + `app/Bootstrap` or Win32 resource helpers to initialize built-in icons, + title-bar branding, or screenshot output roots. - The private `editor/app` CMake include root was removed from `XCEditorCore` and `XCEditor`. App sources and editor app-facing unit files now include through explicit module roots, so app-wide include-root drift is caught by diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 277f43b1..5f4ee135 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -281,7 +281,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Scene/EditorSceneRuntime.cpp app/Services/Project/EditorProjectRuntime.cpp app/Services/Project/ProjectBrowserModel.cpp - app/Support/EmbeddedPngLoader.cpp app/Scene/EditorSceneBridge.cpp ) @@ -291,6 +290,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Host/Win32/Windowing/EditorFloatingWindowPlacement.cpp app/Host/Win32/Windowing/EditorWindowSession.cpp app/Host/Win32/Chrome/EditorWindowChromeController.cpp + app/Host/Win32/Resources/Win32EditorResourceService.cpp app/Host/Win32/Runtime/EditorWindowInputController.cpp app/Host/Win32/System/Win32SystemInteractionHost.cpp app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -326,7 +326,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ${CMAKE_SOURCE_DIR}/engine/include ${CMAKE_SOURCE_DIR}/engine/third_party/stb PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app/Bootstrap ${CMAKE_CURRENT_SOURCE_DIR}/app/Composition ${CMAKE_CURRENT_SOURCE_DIR}/app/Features ${CMAKE_CURRENT_SOURCE_DIR}/app/Rendering diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 1632d024..485c9928 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -7,13 +7,13 @@ #include "EditorWindowManager.h" #include "Diagnostics/Win32CrashTrace.h" #include "System/Win32SystemInteractionHost.h" +#include "Resources/Win32EditorResourceService.h" #include "Windowing/EditorWindow.h" #include "Windowing/EditorWindowHostConfig.h" #include "Windowing/EditorWindowHostRuntime.h" #include "Windowing/EditorWindowMessageDispatcher.h" #include "D3D12EditorWindowRenderRuntime.h" #include "EnvironmentFlags.h" -#include "ExecutablePath.h" #include #include @@ -111,14 +111,13 @@ int RunXCEditor(HINSTANCE hInstance, int nCmdShow) { namespace XCEngine::UI::Editor { -using App::GetExecutableDirectory; - bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_repoRoot = ResolveRepoRootPath(); + m_resourceService = std::make_unique(m_hInstance); EnableDpiAwareness(); - const std::filesystem::path logRoot = GetExecutableDirectory() / "logs"; + const std::filesystem::path logRoot = m_resourceService->GetExecutableDirectory() / "logs"; InitializeUIEditorRuntimeTrace(logRoot); SetUnhandledExceptionFilter(&Application::HandleUnhandledException); AppendUIEditorRuntimeTrace("app", "initialize begin"); @@ -154,6 +153,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { *m_editorContext, *m_windowSystem, *m_renderRuntimeFactory, + *m_resourceService, *m_windowHostRuntime, App::CreateEditorWorkspacePanelRuntimeSet, App::CreateEditorUtilityWindowPanel); @@ -235,6 +235,7 @@ void Application::Shutdown() { m_renderRuntimeFactory.reset(); m_windowHostRuntime.reset(); m_windowSystem.reset(); + m_resourceService.reset(); if (m_editorContext != nullptr) { m_editorContext->SetSystemInteractionHost(nullptr); diff --git a/editor/app/Bootstrap/Application.h b/editor/app/Bootstrap/Application.h index bbc32b2d..30259e67 100644 --- a/editor/app/Bootstrap/Application.h +++ b/editor/app/Bootstrap/Application.h @@ -21,6 +21,7 @@ class EditorWindowHostRuntime; namespace Host { class D3D12EditorWindowRenderRuntimeFactory; +class EditorHostResourceService; } namespace System { @@ -60,6 +61,7 @@ private: std::unique_ptr m_windowSystem = {}; std::unique_ptr m_windowHostRuntime = {}; std::unique_ptr m_renderRuntimeFactory = {}; + std::unique_ptr m_resourceService = {}; std::unique_ptr m_windowManager = {}; std::unique_ptr m_systemInteractionHost = {}; int m_smokeTestFrameLimit = 0; diff --git a/editor/app/Composition/EditorShellRuntime.cpp b/editor/app/Composition/EditorShellRuntime.cpp index 7f6a3bce..035d2410 100644 --- a/editor/app/Composition/EditorShellRuntime.cpp +++ b/editor/app/Composition/EditorShellRuntime.cpp @@ -15,9 +15,10 @@ EditorShellRuntime::EditorShellRuntime(EditorWorkspacePanelRuntimeSet workspaceP void EditorShellRuntime::Initialize( const std::filesystem::path& repoRoot, Rendering::Host::UiTextureHost& textureHost, + Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer) { m_textureHost = &textureHost; - m_builtInIcons.Initialize(textureHost); + m_builtInIcons.Initialize(textureHost, resourceService); m_workspacePanels.Initialize( EditorWorkspacePanelInitializationContext{ .repoRoot = repoRoot, diff --git a/editor/app/Composition/EditorShellRuntime.h b/editor/app/Composition/EditorShellRuntime.h index ae2f0383..ef841476 100644 --- a/editor/app/Composition/EditorShellRuntime.h +++ b/editor/app/Composition/EditorShellRuntime.h @@ -35,6 +35,12 @@ class ViewportRenderHost; } // namespace XCEngine::UI::Editor::Rendering::Host +namespace XCEngine::UI::Editor::Host { + +class EditorHostResourceService; + +} // namespace XCEngine::UI::Editor::Host + namespace XCEngine::Rendering { class RenderContext; @@ -51,6 +57,7 @@ public: void Initialize( const std::filesystem::path& repoRoot, Rendering::Host::UiTextureHost& textureHost, + Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer); void Shutdown(); void ResetInteractionState(); diff --git a/editor/app/Host/Interfaces/EditorHostResourceService.h b/editor/app/Host/Interfaces/EditorHostResourceService.h new file mode 100644 index 00000000..c9cf46b2 --- /dev/null +++ b/editor/app/Host/Interfaces/EditorHostResourceService.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Host { + +enum class EditorHostPngResourceKind : std::uint8_t { + FolderIcon = 0, + GameObjectIcon, + SceneIcon, + Logo, + LogoIcon, + CameraGizmoIcon, + DirectionalLightGizmoIcon, + PointLightGizmoIcon, + SpotLightGizmoIcon, + PlayButtonIcon, + PauseButtonIcon, + StepButtonIcon, +}; + +struct EditorHostResourceBytes { + const std::uint8_t* data = nullptr; + std::size_t size = 0u; + + bool IsValid() const { + return data != nullptr && size > 0u; + } +}; + +class EditorHostResourceService { +public: + virtual ~EditorHostResourceService() = default; + + virtual bool TryLoadPngResource( + EditorHostPngResourceKind kind, + EditorHostResourceBytes& outBytes, + std::string& outError) const = 0; + virtual std::filesystem::path GetExecutableDirectory() const = 0; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Host/Interfaces/HostFwd.h b/editor/app/Host/Interfaces/HostFwd.h index 17fea4e2..dc6d4127 100644 --- a/editor/app/Host/Interfaces/HostFwd.h +++ b/editor/app/Host/Interfaces/HostFwd.h @@ -6,3 +6,9 @@ class UiTextureHost; class ViewportRenderHost; } // namespace XCEngine::UI::Editor::Rendering::Host + +namespace XCEngine::UI::Editor::Host { + +class EditorHostResourceService; + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Host/Win32/Resources/Win32EditorResourceService.cpp b/editor/app/Host/Win32/Resources/Win32EditorResourceService.cpp new file mode 100644 index 00000000..e2e05da2 --- /dev/null +++ b/editor/app/Host/Win32/Resources/Win32EditorResourceService.cpp @@ -0,0 +1,119 @@ +#include "Resources/Win32EditorResourceService.h" + +#include "EditorResources.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +int ResolvePngResourceId(EditorHostPngResourceKind kind) { + switch (kind) { + case EditorHostPngResourceKind::FolderIcon: + return IDR_PNG_FOLDER_ICON; + case EditorHostPngResourceKind::GameObjectIcon: + return IDR_PNG_GAMEOBJECT_ICON; + case EditorHostPngResourceKind::SceneIcon: + return IDR_PNG_SCENE_ICON; + case EditorHostPngResourceKind::Logo: + return IDR_PNG_LOGO; + case EditorHostPngResourceKind::LogoIcon: + return IDR_PNG_LOGO_ICON; + case EditorHostPngResourceKind::CameraGizmoIcon: + return IDR_PNG_CAMERA_GIZMO_ICON; + case EditorHostPngResourceKind::DirectionalLightGizmoIcon: + return IDR_PNG_DIRECTIONAL_LIGHT_GIZMO_ICON; + case EditorHostPngResourceKind::PointLightGizmoIcon: + return IDR_PNG_POINT_LIGHT_GIZMO_ICON; + case EditorHostPngResourceKind::SpotLightGizmoIcon: + return IDR_PNG_SPOT_LIGHT_GIZMO_ICON; + case EditorHostPngResourceKind::PlayButtonIcon: + return IDR_PNG_PLAY_BUTTON_ICON; + case EditorHostPngResourceKind::PauseButtonIcon: + return IDR_PNG_PAUSE_BUTTON_ICON; + case EditorHostPngResourceKind::StepButtonIcon: + return IDR_PNG_STEP_BUTTON_ICON; + default: + return 0; + } +} + +} // namespace + +Win32EditorResourceService::Win32EditorResourceService(HINSTANCE instance) + : m_instance(instance) {} + +bool Win32EditorResourceService::TryLoadPngResource( + EditorHostPngResourceKind kind, + EditorHostResourceBytes& outBytes, + std::string& outError) const { + outBytes = {}; + outError.clear(); + + const int resourceId = ResolvePngResourceId(kind); + if (resourceId == 0) { + outError = "Unknown PNG resource kind."; + return false; + } + + HINSTANCE module = m_instance; + if (module == nullptr) { + module = GetModuleHandleW(nullptr); + } + if (module == nullptr) { + outError = "GetModuleHandleW(nullptr) returned null."; + return false; + } + + HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); + if (resource == nullptr) { + outError = "FindResourceW failed."; + return false; + } + + HGLOBAL resourceData = LoadResource(module, resource); + if (resourceData == nullptr) { + outError = "LoadResource failed."; + return false; + } + + const DWORD resourceSize = SizeofResource(module, resource); + if (resourceSize == 0u) { + outError = "SizeofResource returned zero."; + return false; + } + + const void* lockedBytes = LockResource(resourceData); + if (lockedBytes == nullptr) { + outError = "LockResource failed."; + return false; + } + + outBytes.data = reinterpret_cast(lockedBytes); + outBytes.size = static_cast(resourceSize); + return true; +} + +std::filesystem::path Win32EditorResourceService::GetExecutableDirectory() const { + std::vector buffer(MAX_PATH); + while (true) { + const DWORD copied = ::GetModuleFileNameW( + nullptr, + buffer.data(), + static_cast(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); + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Host/Win32/Resources/Win32EditorResourceService.h b/editor/app/Host/Win32/Resources/Win32EditorResourceService.h new file mode 100644 index 00000000..e0d2df70 --- /dev/null +++ b/editor/app/Host/Win32/Resources/Win32EditorResourceService.h @@ -0,0 +1,27 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "EditorHostResourceService.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +class Win32EditorResourceService final : public EditorHostResourceService { +public: + explicit Win32EditorResourceService(HINSTANCE instance); + + bool TryLoadPngResource( + EditorHostPngResourceKind kind, + EditorHostResourceBytes& outBytes, + std::string& outError) const override; + std::filesystem::path GetExecutableDirectory() const override; + +private: + HINSTANCE m_instance = nullptr; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Host/Win32/System/Win32StringEncoding.h b/editor/app/Host/Win32/System/Win32StringEncoding.h new file mode 100644 index 00000000..3c8d840f --- /dev/null +++ b/editor/app/Host/Win32/System/Win32StringEncoding.h @@ -0,0 +1,82 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App { + +inline std::wstring Utf8ToWide(std::string_view text) { + if (text.empty()) { + return {}; + } + + const int requiredChars = MultiByteToWideChar( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + nullptr, + 0); + if (requiredChars <= 0) { + return {}; + } + + std::wstring wide(static_cast(requiredChars), L'\0'); + const int convertedChars = MultiByteToWideChar( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + wide.data(), + requiredChars); + if (convertedChars <= 0) { + return {}; + } + + wide.resize(static_cast(convertedChars)); + return wide; +} + +inline std::string WideToUtf8(std::wstring_view text) { + if (text.empty()) { + return {}; + } + + const int requiredBytes = WideCharToMultiByte( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + nullptr, + 0, + nullptr, + nullptr); + if (requiredBytes <= 0) { + return {}; + } + + std::string utf8(static_cast(requiredBytes), '\0'); + const int convertedBytes = WideCharToMultiByte( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + utf8.data(), + requiredBytes, + nullptr, + nullptr); + if (convertedBytes <= 0) { + return {}; + } + + utf8.resize(static_cast(convertedBytes)); + return utf8; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Host/Win32/System/Win32SystemInteractionHost.cpp b/editor/app/Host/Win32/System/Win32SystemInteractionHost.cpp index 1652e6c1..737200df 100644 --- a/editor/app/Host/Win32/System/Win32SystemInteractionHost.cpp +++ b/editor/app/Host/Win32/System/Win32SystemInteractionHost.cpp @@ -1,6 +1,6 @@ #include "System/Win32SystemInteractionHost.h" -#include "StringEncoding.h" +#include "System/Win32StringEncoding.h" #include #include diff --git a/editor/app/Host/Win32/Windowing/EditorWindowSupport.h b/editor/app/Host/Win32/Windowing/EditorWindowSupport.h index cd7dbbc9..f966b53b 100644 --- a/editor/app/Host/Win32/Windowing/EditorWindowSupport.h +++ b/editor/app/Host/Win32/Windowing/EditorWindowSupport.h @@ -1,6 +1,6 @@ #pragma once -#include "StringEncoding.h" +#include "System/Win32StringEncoding.h" #include "TextFormat.h" #include diff --git a/editor/app/Rendering/Assets/BuiltInIcons.cpp b/editor/app/Rendering/Assets/BuiltInIcons.cpp index f364ef90..daa19c72 100644 --- a/editor/app/Rendering/Assets/BuiltInIcons.cpp +++ b/editor/app/Rendering/Assets/BuiltInIcons.cpp @@ -1,14 +1,13 @@ #include "BuiltInIcons.h" -#include "EditorResources.h" +#include "EditorHostResourceService.h" #include "UiTextureHost.h" -#include "EmbeddedPngLoader.h" -#include "StringEncoding.h" #include #include #include +#include #include #include #include @@ -64,12 +63,19 @@ void AppendLoadError( void LoadEmbeddedIconTexture( Rendering::Host::UiTextureHost& renderer, - UINT resourceId, + Host::EditorHostResourceService& resourceService, + Host::EditorHostPngResourceKind resourceKind, std::string_view label, ::XCEngine::UI::UITextureHandle& outTexture, std::ostringstream& errorStream) { std::string error = {}; - if (!LoadEmbeddedPngTexture(renderer, resourceId, outTexture, error)) { + Host::EditorHostResourceBytes bytes = {}; + if (!resourceService.TryLoadPngResource(resourceKind, bytes, error)) { + AppendLoadError(errorStream, label, error); + return; + } + + if (!renderer.LoadTextureFromMemory(bytes.data, bytes.size, outTexture, error)) { AppendLoadError(errorStream, label, error); } } @@ -115,9 +121,14 @@ bool QuerySourceFileFingerprint( } std::string NormalizePathKey(const std::filesystem::path& path) { - std::wstring key = path.lexically_normal().generic_wstring(); - std::transform(key.begin(), key.end(), key.begin(), ::towlower); - return WideToUtf8(key); + const std::u8string normalized = path.lexically_normal().generic_u8string(); + std::string key = {}; + key.reserve(normalized.size()); + for (const char8_t character : normalized) { + const unsigned char byte = static_cast(character); + key.push_back(static_cast(std::tolower(byte))); + } + return key; } std::uint64_t ComputeFnv1a64(const std::string& value) { @@ -191,7 +202,7 @@ bool ResolvePreviewDiskCacheEntry( FormatHashHex(ComputeFnv1a64(outEntry.relativePathKey)) + ".thumb"; outEntry.filePath = (canonicalRoot / L".xceditor" / L"thumbs" / - std::filesystem::path(Utf8ToWide(hashFileName))).lexically_normal(); + std::filesystem::path(hashFileName)).lexically_normal(); return true; } @@ -494,7 +505,9 @@ bool DecodeTextureFromFile( } // namespace -void BuiltInIcons::Initialize(Rendering::Host::UiTextureHost& renderer) { +void BuiltInIcons::Initialize( + Rendering::Host::UiTextureHost& renderer, + Host::EditorHostResourceService& resourceService) { Shutdown(); m_renderer = &renderer; @@ -503,61 +516,71 @@ void BuiltInIcons::Initialize(Rendering::Host::UiTextureHost& renderer) { std::ostringstream errorStream = {}; LoadEmbeddedIconTexture( renderer, - IDR_PNG_FOLDER_ICON, + resourceService, + Host::EditorHostPngResourceKind::FolderIcon, "folder_icon.png", m_folderIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_GAMEOBJECT_ICON, + resourceService, + Host::EditorHostPngResourceKind::GameObjectIcon, "gameobject_icon.png", m_gameObjectIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_SCENE_ICON, + resourceService, + Host::EditorHostPngResourceKind::SceneIcon, "scene_icon.png", m_sceneIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_CAMERA_GIZMO_ICON, + resourceService, + Host::EditorHostPngResourceKind::CameraGizmoIcon, "camera_gizmo.png", m_cameraGizmoIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_DIRECTIONAL_LIGHT_GIZMO_ICON, + resourceService, + Host::EditorHostPngResourceKind::DirectionalLightGizmoIcon, "directional_light_gizmo.png", m_directionalLightGizmoIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_POINT_LIGHT_GIZMO_ICON, + resourceService, + Host::EditorHostPngResourceKind::PointLightGizmoIcon, "point_light_gizmo.png", m_pointLightGizmoIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_SPOT_LIGHT_GIZMO_ICON, + resourceService, + Host::EditorHostPngResourceKind::SpotLightGizmoIcon, "spot_light_gizmo.png", m_spotLightGizmoIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_PLAY_BUTTON_ICON, + resourceService, + Host::EditorHostPngResourceKind::PlayButtonIcon, "play_button.png", m_playButtonIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_PAUSE_BUTTON_ICON, + resourceService, + Host::EditorHostPngResourceKind::PauseButtonIcon, "pause_button.png", m_pauseButtonIcon, errorStream); LoadEmbeddedIconTexture( renderer, - IDR_PNG_STEP_BUTTON_ICON, + resourceService, + Host::EditorHostPngResourceKind::StepButtonIcon, "step_button.png", m_stepButtonIcon, errorStream); diff --git a/editor/app/Rendering/Assets/BuiltInIcons.h b/editor/app/Rendering/Assets/BuiltInIcons.h index 3cb70881..b191e01a 100644 --- a/editor/app/Rendering/Assets/BuiltInIcons.h +++ b/editor/app/Rendering/Assets/BuiltInIcons.h @@ -32,7 +32,9 @@ enum class BuiltInIconKind : std::uint8_t { class BuiltInIcons { public: - void Initialize(Rendering::Host::UiTextureHost& renderer); + void Initialize( + Rendering::Host::UiTextureHost& renderer, + Host::EditorHostResourceService& resourceService); void Shutdown(); void BeginFrame(); diff --git a/editor/app/Support/EmbeddedPngLoader.cpp b/editor/app/Support/EmbeddedPngLoader.cpp deleted file mode 100644 index e5de4357..00000000 --- a/editor/app/Support/EmbeddedPngLoader.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "EmbeddedPngLoader.h" - -#include "UiTextureHost.h" - -namespace XCEngine::UI::Editor::App { - -bool LoadEmbeddedPngBytes( - UINT resourceId, - const std::uint8_t*& outData, - std::size_t& outSize, - std::string& outError) { - outData = nullptr; - outSize = 0u; - outError.clear(); - - HMODULE module = GetModuleHandleW(nullptr); - if (module == nullptr) { - outError = "GetModuleHandleW(nullptr) returned null."; - return false; - } - - HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); - if (resource == nullptr) { - outError = "FindResourceW failed."; - return false; - } - - HGLOBAL resourceData = LoadResource(module, resource); - if (resourceData == nullptr) { - outError = "LoadResource failed."; - return false; - } - - const DWORD resourceSize = SizeofResource(module, resource); - if (resourceSize == 0u) { - outError = "SizeofResource returned zero."; - return false; - } - - const void* lockedBytes = LockResource(resourceData); - if (lockedBytes == nullptr) { - outError = "LockResource failed."; - return false; - } - - outData = reinterpret_cast(lockedBytes); - outSize = static_cast(resourceSize); - return true; -} - -bool LoadEmbeddedPngTexture( - Rendering::Host::UiTextureHost& renderer, - UINT resourceId, - ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError) { - const std::uint8_t* bytes = nullptr; - std::size_t byteCount = 0u; - if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, outError)) { - return false; - } - - return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Support/EmbeddedPngLoader.h b/editor/app/Support/EmbeddedPngLoader.h deleted file mode 100644 index 58c95160..00000000 --- a/editor/app/Support/EmbeddedPngLoader.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "HostFwd.h" - -#include - -#include -#include -#include - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include - -namespace XCEngine::UI::Editor::App { - -bool LoadEmbeddedPngBytes( - UINT resourceId, - const std::uint8_t*& outData, - std::size_t& outSize, - std::string& outError); - -bool LoadEmbeddedPngTexture( - Rendering::Host::UiTextureHost& renderer, - UINT resourceId, - ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError); - -} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Support/ExecutablePath.h b/editor/app/Support/ExecutablePath.h deleted file mode 100644 index fafd11bb..00000000 --- a/editor/app/Support/ExecutablePath.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace XCEngine::UI::Editor::App { - -inline std::filesystem::path GetExecutableDirectory() { - std::vector buffer(MAX_PATH); - while (true) { - const DWORD copied = ::GetModuleFileNameW( - nullptr, - buffer.data(), - static_cast(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); - } -} - -} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Support/StringEncoding.h b/editor/app/Support/StringEncoding.h index de2ab2c7..96c7ae94 100644 --- a/editor/app/Support/StringEncoding.h +++ b/editor/app/Support/StringEncoding.h @@ -1,10 +1,10 @@ #pragma once +#include +#include #include #include -#include - namespace XCEngine::UI::Editor::App { inline std::wstring Utf8ToWide(std::string_view text) { @@ -12,31 +12,12 @@ inline std::wstring Utf8ToWide(std::string_view text) { return {}; } - const int requiredChars = MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - nullptr, - 0); - if (requiredChars <= 0) { + try { + std::wstring_convert, wchar_t> converter = {}; + return converter.from_bytes(text.data(), text.data() + text.size()); + } catch (const std::range_error&) { return {}; } - - std::wstring wide(static_cast(requiredChars), L'\0'); - const int convertedChars = MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - wide.data(), - requiredChars); - if (convertedChars <= 0) { - return {}; - } - - wide.resize(static_cast(convertedChars)); - return wide; } inline std::string WideToUtf8(std::wstring_view text) { @@ -44,35 +25,12 @@ inline std::string WideToUtf8(std::wstring_view text) { return {}; } - const int requiredBytes = WideCharToMultiByte( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - nullptr, - 0, - nullptr, - nullptr); - if (requiredBytes <= 0) { + try { + std::wstring_convert, wchar_t> converter = {}; + return converter.to_bytes(text.data(), text.data() + text.size()); + } catch (const std::range_error&) { return {}; } - - std::string utf8(static_cast(requiredBytes), '\0'); - const int convertedBytes = WideCharToMultiByte( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - utf8.data(), - requiredBytes, - nullptr, - nullptr); - if (convertedBytes <= 0) { - return {}; - } - - utf8.resize(static_cast(convertedBytes)); - return utf8; } } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Content/EditorWindowContentController.h b/editor/app/Windowing/Content/EditorWindowContentController.h index 8bf0f4b2..63bed7df 100644 --- a/editor/app/Windowing/Content/EditorWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWindowContentController.h @@ -42,6 +42,12 @@ class ViewportRenderHost; } // namespace XCEngine::UI::Editor::Rendering::Host +namespace XCEngine::UI::Editor::Host { + +class EditorHostResourceService; + +} // namespace XCEngine::UI::Editor::Host + namespace XCEngine::UI::Editor::App { class EditorContext; @@ -59,6 +65,7 @@ struct EditorWindowContentInitializationContext { const std::filesystem::path& repoRoot; EditorContext& editorContext; Rendering::Host::UiTextureHost& textureHost; + Host::EditorHostResourceService& resourceService; UIEditorTextMeasurer& textMeasurer; Rendering::Host::ViewportRenderHost& viewportRenderer; }; diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index 699eff69..01ce2c33 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -111,7 +111,11 @@ bool EditorWorkspaceWindowContentController::TryBuildAuthoritativeWorkspaceContr void EditorWorkspaceWindowContentController::Initialize( const EditorWindowContentInitializationContext& context) { - m_shellRuntime.Initialize(context.repoRoot, context.textureHost, context.textMeasurer); + m_shellRuntime.Initialize( + context.repoRoot, + context.textureHost, + context.resourceService, + context.textMeasurer); m_shellRuntime.AttachViewportWindowRenderer(context.viewportRenderer); } diff --git a/editor/app/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp index 7db69c6e..7ccee8a8 100644 --- a/editor/app/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -23,11 +23,13 @@ EditorWindowManager::EditorWindowManager( EditorContext& editorContext, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, + Host::EditorHostResourceService& resourceService, EditorWindowHostRuntimeServices& hostRuntime, EditorWorkspacePanelRuntimeSetFactory workspacePanelFactory, EditorUtilityWindowPanelFactory utilityPanelFactory) : m_editorContext(editorContext) , m_renderRuntimeFactory(renderRuntimeFactory) + , m_resourceService(resourceService) , m_hostRuntime(hostRuntime) { m_contentFactory = CreateDefaultEditorWindowContentFactory( windowSystem, @@ -79,6 +81,7 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( params.primary, std::make_unique( m_editorContext, + m_resourceService, m_contentFactory->CreateWorkspaceContentController(windowState), m_renderRuntimeFactory.CreateWindowRenderRuntime())); EditorHostWindow* const window = windowInstance.get(); @@ -107,6 +110,7 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow( params.primary, std::make_unique( m_editorContext, + m_resourceService, m_contentFactory->CreateUtilityContentController(descriptor), m_renderRuntimeFactory.CreateWindowRenderRuntime())); EditorHostWindow* const window = windowInstance.get(); diff --git a/editor/app/Windowing/EditorWindowManager.h b/editor/app/Windowing/EditorWindowManager.h index eab9e05a..16a24d7a 100644 --- a/editor/app/Windowing/EditorWindowManager.h +++ b/editor/app/Windowing/EditorWindowManager.h @@ -25,6 +25,12 @@ class EditorWindowRenderRuntimeFactory; } // namespace XCEngine::UI::Editor +namespace XCEngine::UI::Editor::Host { + +class EditorHostResourceService; + +} // namespace XCEngine::UI::Editor::Host + namespace XCEngine::UI::Editor::App { class EditorContext; @@ -42,6 +48,7 @@ public: EditorContext& editorContext, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, + Host::EditorHostResourceService& resourceService, EditorWindowHostRuntimeServices& hostRuntime, EditorWorkspacePanelRuntimeSetFactory workspacePanelFactory, EditorUtilityWindowPanelFactory utilityPanelFactory); @@ -92,6 +99,7 @@ private: EditorContext& m_editorContext; Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory; + Host::EditorHostResourceService& m_resourceService; EditorWindowHostRuntimeServices& m_hostRuntime; std::unique_ptr m_contentFactory = {}; std::unique_ptr m_lifecycleCoordinator = {}; diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp index 69680b77..5bc18212 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp @@ -1,7 +1,6 @@ #include "Runtime/EditorWindowRuntimeController.h" -#include "EditorResources.h" -#include "EmbeddedPngLoader.h" +#include "EditorHostResourceService.h" #include "TextFormat.h" #include @@ -17,7 +16,6 @@ namespace XCEngine::UI::Editor::App { -using App::LoadEmbeddedPngTexture; using App::TruncateText; namespace { @@ -29,13 +27,29 @@ void LogRuntimeTrace(std::string_view channel, std::string_view message) { AppendUIEditorRuntimeTrace(channel, message); } +bool LoadHostPngTexture( + Host::EditorHostResourceService& resourceService, + Rendering::Host::UiTextureHost& textureHost, + Host::EditorHostPngResourceKind resourceKind, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) { + Host::EditorHostResourceBytes bytes = {}; + if (!resourceService.TryLoadPngResource(resourceKind, bytes, outError)) { + return false; + } + + return textureHost.LoadTextureFromMemory(bytes.data, bytes.size, outTexture, outError); +} + } EditorWindowRuntimeController::EditorWindowRuntimeController( EditorContext& editorContext, + Host::EditorHostResourceService& resourceService, std::unique_ptr contentController, std::unique_ptr renderRuntime) : m_editorContext(editorContext) + , m_resourceService(resourceService) , m_renderRuntime(std::move(renderRuntime)) , m_contentController(std::move(contentController)) {} @@ -156,6 +170,7 @@ bool EditorWindowRuntimeController::Initialize( .repoRoot = repoRoot, .editorContext = m_editorContext, .textureHost = m_renderRuntime->GetTextureHost(), + .resourceService = m_resourceService, .textMeasurer = m_renderRuntime->GetTextMeasurer(), .viewportRenderer = m_renderRuntime->GetViewportRenderHost(), }); @@ -163,9 +178,10 @@ bool EditorWindowRuntimeController::Initialize( initializeResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; - if (!LoadEmbeddedPngTexture( + if (!LoadHostPngTexture( + m_resourceService, m_renderRuntime->GetTextureHost(), - IDR_PNG_LOGO_ICON, + Host::EditorHostPngResourceKind::LogoIcon, m_titleBarLogoIcon, titleBarLogoError)) { LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); @@ -183,7 +199,9 @@ bool EditorWindowRuntimeController::Initialize( ResetFrameTiming(); m_ready = true; - m_screenshotController.Initialize(captureRoot); + m_screenshotController.Initialize( + captureRoot, + m_resourceService.GetExecutableDirectory()); if (autoCaptureOnStartup) { m_screenshotController.RequestCapture("startup"); m_contentController->NotifyStartupCaptureRequested(m_editorContext); diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h index 92c47444..9cfbdbb6 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h @@ -29,10 +29,21 @@ namespace XCEngine::UI::Editor::App { class EditorContext; +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::Host { + +class EditorHostResourceService; + +} // namespace XCEngine::UI::Editor::Host + +namespace XCEngine::UI::Editor::App { + class EditorWindowRuntimeController final { public: EditorWindowRuntimeController( EditorContext& editorContext, + Host::EditorHostResourceService& resourceService, std::unique_ptr contentController, std::unique_ptr renderRuntime); ~EditorWindowRuntimeController(); @@ -97,6 +108,7 @@ private: void RefreshDisplayedFrameStats(); EditorContext& m_editorContext; + Host::EditorHostResourceService& m_resourceService; std::unique_ptr m_renderRuntime = {}; EditorWindowScreenshotController m_screenshotController = {}; ::XCEngine::UI::UITextureHandle m_titleBarLogoIcon = {}; diff --git a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp index ee16ae76..ae96c896 100644 --- a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp @@ -1,7 +1,5 @@ #include "Runtime/EditorWindowScreenshotController.h" -#include "ExecutablePath.h" - #include #include #include @@ -11,8 +9,10 @@ namespace XCEngine::UI::Editor::App { namespace { -std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& requestedCaptureRoot) { - std::filesystem::path captureRoot = App::GetExecutableDirectory() / "captures"; +std::filesystem::path ResolveBuildCaptureRoot( + const std::filesystem::path& requestedCaptureRoot, + const std::filesystem::path& executableDirectory) { + std::filesystem::path captureRoot = executableDirectory / "captures"; const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); if (!scenarioPath.empty() && scenarioPath != "captures") { captureRoot /= scenarioPath; @@ -23,8 +23,10 @@ std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& reque } // namespace -void EditorWindowScreenshotController::Initialize(const std::filesystem::path& captureRoot) { - m_captureRoot = ResolveBuildCaptureRoot(captureRoot); +void EditorWindowScreenshotController::Initialize( + const std::filesystem::path& captureRoot, + const std::filesystem::path& executableDirectory) { + m_captureRoot = ResolveBuildCaptureRoot(captureRoot, executableDirectory); m_historyRoot = (m_captureRoot / "history").lexically_normal(); m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal(); m_activeHistoryCapturePath.clear(); diff --git a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.h b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.h index 51ab1002..8b6bb074 100644 --- a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.h +++ b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.h @@ -13,7 +13,9 @@ namespace XCEngine::UI::Editor::App { class EditorWindowScreenshotController { public: - void Initialize(const std::filesystem::path& captureRoot); + void Initialize( + const std::filesystem::path& captureRoot, + const std::filesystem::path& executableDirectory); void Shutdown(); void RequestCapture(std::string reason);