相机更新

This commit is contained in:
Rowland 2026-06-15 09:54:09 +08:00
parent bcec8b6a8d
commit 97b704428a
9 changed files with 1515 additions and 240 deletions

View File

@ -4350,6 +4350,7 @@ public:
[[nodiscard]] bool OpenProject(const std::filesystem::path& projectRoot) override;
[[nodiscard]] bool RenamePath(const std::filesystem::path& relativePath, const std::filesystem::path& newName) override;
[[nodiscard]] bool MovePath(const std::filesystem::path& relativePath, const std::filesystem::path& targetDirectory) override;
[[nodiscard]] bool MovePathTo(const std::filesystem::path& relativePath, const std::filesystem::path& targetRelativePath) override;
[[nodiscard]] bool DeletePath(const std::filesystem::path& relativePath) override;
bool ApplyExternalFileChanges(const std::vector<MetaCoreAssetChangeEvent>& changes) override;
[[nodiscard]] const std::vector<MetaCoreAssetRecord>& GetAssetRegistry() const override { return AssetRecords_; }
@ -4403,6 +4404,8 @@ public:
bool Refresh() override;
bool CreateFolder(const std::filesystem::path& relativeDirectory) override;
bool CreateMaterialAsset(const std::filesystem::path& relativeMaterialPath) override;
bool CreateEmptySceneAsset(const std::filesystem::path& relativeScenePath, bool makeStartupScene) override;
bool RegisterScenePath(const std::filesystem::path& relativeScenePath, bool makeStartupScene) override;
bool SetStartupScenePath(const std::filesystem::path& relativeScenePath) override;
@ -4570,49 +4573,7 @@ bool MetaCoreBuiltinAssetDatabaseService::RenamePath(
const std::filesystem::path normalizedPath = relativePath.lexically_normal();
const std::filesystem::path targetPath = (normalizedPath.parent_path() / newName).lexically_normal();
if (MetaCoreIsUnsafeRelativePath(targetPath)) {
return false;
}
const std::filesystem::path absoluteSourcePath = Project_.RootPath / normalizedPath;
const std::filesystem::path absoluteTargetPath = Project_.RootPath / targetPath;
if (!std::filesystem::exists(absoluteSourcePath) || std::filesystem::exists(absoluteTargetPath)) {
return false;
}
std::error_code errorCode;
std::filesystem::rename(absoluteSourcePath, absoluteTargetPath, errorCode);
if (errorCode) {
return false;
}
MetaCoreMoveSidecarIfPresent(Project_.RootPath, normalizedPath, targetPath);
MetaCoreUpdateMovedMetadata(Project_.RootPath, normalizedPath, targetPath);
const auto remapPath = [&](std::filesystem::path& pathValue) {
if (pathValue == normalizedPath) {
pathValue = targetPath;
} else {
const auto normalizedValue = pathValue.lexically_normal();
const auto normalizedString = normalizedValue.generic_string();
const auto sourcePrefix = normalizedPath.generic_string() + "/";
if (normalizedString.rfind(sourcePrefix, 0) == 0) {
pathValue = (targetPath / normalizedValue.lexically_relative(normalizedPath)).lexically_normal();
}
}
};
remapPath(Project_.StartupScenePath);
for (auto& scenePath : Project_.ScenePaths) {
remapPath(scenePath);
}
std::sort(Project_.ScenePaths.begin(), Project_.ScenePaths.end());
Project_.ScenePaths.erase(std::unique(Project_.ScenePaths.begin(), Project_.ScenePaths.end()), Project_.ScenePaths.end());
SaveProjectDescriptor();
const bool refreshed = Refresh();
RescanHotReloadBaseline();
return refreshed;
return MovePathTo(normalizedPath, targetPath);
}
bool MetaCoreBuiltinAssetDatabaseService::MovePath(
@ -4633,42 +4594,65 @@ bool MetaCoreBuiltinAssetDatabaseService::MovePath(
return true;
}
const std::filesystem::path absoluteSourcePath = Project_.RootPath / normalizedSourcePath;
const std::filesystem::path absoluteTargetDirectory = Project_.RootPath / normalizedTargetDirectory;
if (!std::filesystem::exists(absoluteSourcePath) || !std::filesystem::exists(absoluteTargetDirectory)) {
if (!std::filesystem::exists(absoluteTargetDirectory) || !std::filesystem::is_directory(absoluteTargetDirectory)) {
return false;
}
const std::filesystem::path targetPath = (normalizedTargetDirectory / normalizedSourcePath.filename()).lexically_normal();
if (MetaCoreIsUnsafeRelativePath(targetPath) || std::filesystem::exists(Project_.RootPath / targetPath)) {
return MovePathTo(normalizedSourcePath, targetPath);
}
bool MetaCoreBuiltinAssetDatabaseService::MovePathTo(
const std::filesystem::path& relativePath,
const std::filesystem::path& targetRelativePath
) {
if (!HasProject() ||
MetaCoreIsUnsafeRelativePath(relativePath) ||
MetaCoreIsUnsafeRelativePath(targetRelativePath) ||
relativePath.empty() ||
targetRelativePath.empty()) {
return false;
}
auto remapPath = [&](std::filesystem::path& pathValue) {
if (pathValue == normalizedSourcePath) {
pathValue = targetPath;
} else {
const auto normalizedValue = pathValue.lexically_normal();
const auto normalizedString = normalizedValue.generic_string();
const auto sourcePrefix = normalizedSourcePath.generic_string() + "/";
if (normalizedString.rfind(sourcePrefix, 0) == 0) {
pathValue = (targetPath / normalizedValue.lexically_relative(normalizedSourcePath)).lexically_normal();
}
const std::filesystem::path normalizedSourcePath = relativePath.lexically_normal();
const std::filesystem::path normalizedTargetPath = targetRelativePath.lexically_normal();
if (normalizedSourcePath == normalizedTargetPath) {
return true;
}
const std::filesystem::path absoluteSourcePath = Project_.RootPath / normalizedSourcePath;
const std::filesystem::path absoluteTargetPath = Project_.RootPath / normalizedTargetPath;
if (!std::filesystem::exists(absoluteSourcePath) || std::filesystem::exists(absoluteTargetPath)) {
return false;
}
if (std::filesystem::is_directory(absoluteSourcePath)) {
const std::string sourcePrefix = normalizedSourcePath.generic_string() + "/";
const std::string targetString = normalizedTargetPath.generic_string() + "/";
if (targetString.rfind(sourcePrefix, 0) == 0) {
return false;
}
};
}
std::error_code errorCode;
std::filesystem::rename(absoluteSourcePath, Project_.RootPath / targetPath, errorCode);
std::filesystem::create_directories(absoluteTargetPath.parent_path(), errorCode);
if (errorCode) {
return false;
}
MetaCoreMoveSidecarIfPresent(Project_.RootPath, normalizedSourcePath, targetPath);
MetaCoreUpdateMovedMetadata(Project_.RootPath, normalizedSourcePath, targetPath);
errorCode.clear();
std::filesystem::rename(absoluteSourcePath, absoluteTargetPath, errorCode);
if (errorCode) {
return false;
}
remapPath(Project_.StartupScenePath);
MetaCoreMoveSidecarIfPresent(Project_.RootPath, normalizedSourcePath, normalizedTargetPath);
MetaCoreUpdateMovedMetadata(Project_.RootPath, normalizedSourcePath, normalizedTargetPath);
Project_.StartupScenePath = MetaCoreRemapRelativePath(Project_.StartupScenePath, normalizedSourcePath, normalizedTargetPath);
for (auto& scenePath : Project_.ScenePaths) {
remapPath(scenePath);
scenePath = MetaCoreRemapRelativePath(scenePath, normalizedSourcePath, normalizedTargetPath);
}
std::sort(Project_.ScenePaths.begin(), Project_.ScenePaths.end());
Project_.ScenePaths.erase(std::unique(Project_.ScenePaths.begin(), Project_.ScenePaths.end()), Project_.ScenePaths.end());
@ -5031,6 +5015,108 @@ bool MetaCoreBuiltinAssetDatabaseService::CreateFolder(const std::filesystem::pa
return std::filesystem::create_directories(Project_.RootPath / relativeDirectory.lexically_normal());
}
bool MetaCoreBuiltinAssetDatabaseService::CreateMaterialAsset(const std::filesystem::path& relativeMaterialPath) {
if (!HasProject() || ReflectionRegistry_ == nullptr || MetaCoreIsUnsafeRelativePath(relativeMaterialPath)) {
return false;
}
const std::filesystem::path normalizedPath = relativeMaterialPath.lexically_normal();
if (!MetaCoreIsMaterialPath(normalizedPath)) {
return false;
}
const std::filesystem::path absolutePath = Project_.RootPath / normalizedPath;
if (std::filesystem::exists(absolutePath)) {
return false;
}
std::error_code errorCode;
std::filesystem::create_directories(absolutePath.parent_path(), errorCode);
if (errorCode) {
return false;
}
MetaCoreMaterialAssetDocument materialDocument;
materialDocument.AssetGuid = MetaCoreAssetGuid::Generate();
materialDocument.Name = normalizedPath.stem().stem().string();
if (!MetaCoreSceneSerializer::SaveMaterialToJson(absolutePath, materialDocument, ReflectionRegistry_->GetTypeRegistry())) {
return false;
}
MetaCoreAssetMetadataDocument metadata;
metadata.AssetGuid = materialDocument.AssetGuid;
metadata.AssetType = "material";
metadata.ImporterId = "MaterialImporter";
metadata.SourcePath = normalizedPath.generic_string();
metadata.PackagePath = normalizedPath.generic_string();
metadata.SourceHash = MetaCoreHashFile(absolutePath).value_or(0);
if (!MetaCoreWriteMetaJson(MetaCoreBuildMetaPath(absolutePath), metadata)) {
return false;
}
const bool refreshed = Refresh();
RescanHotReloadBaseline();
return refreshed;
}
bool MetaCoreBuiltinAssetDatabaseService::CreateEmptySceneAsset(
const std::filesystem::path& relativeScenePath,
bool makeStartupScene
) {
if (!HasProject() || ReflectionRegistry_ == nullptr || MetaCoreIsUnsafeRelativePath(relativeScenePath)) {
return false;
}
const std::filesystem::path normalizedPath = relativeScenePath.lexically_normal();
if (!MetaCoreIsScenePath(normalizedPath)) {
return false;
}
const std::filesystem::path absolutePath = Project_.RootPath / normalizedPath;
if (std::filesystem::exists(absolutePath)) {
return false;
}
std::error_code errorCode;
std::filesystem::create_directories(absolutePath.parent_path(), errorCode);
if (errorCode) {
return false;
}
const MetaCoreAssetGuid sceneGuid = MetaCoreAssetGuid::Generate();
MetaCoreSceneDocument sceneDocument;
sceneDocument.Name = MetaCoreSceneDisplayNameFromPath(normalizedPath);
sceneDocument.GameObjects = {};
if (!MetaCoreSceneSerializer::SaveSceneToJson(absolutePath, sceneDocument, ReflectionRegistry_->GetTypeRegistry())) {
return false;
}
MetaCoreAssetMetadataDocument metadata;
metadata.AssetGuid = sceneGuid;
metadata.AssetType = "scene";
metadata.ImporterId = "SceneImporter";
metadata.SourcePath = normalizedPath.generic_string();
metadata.PackagePath = normalizedPath.generic_string();
metadata.SourceHash = MetaCoreHashFile(absolutePath).value_or(0);
if (!MetaCoreWriteMetaJson(MetaCoreBuildMetaPath(absolutePath), metadata)) {
return false;
}
if (std::find(Project_.ScenePaths.begin(), Project_.ScenePaths.end(), normalizedPath) == Project_.ScenePaths.end()) {
Project_.ScenePaths.push_back(normalizedPath);
std::sort(Project_.ScenePaths.begin(), Project_.ScenePaths.end());
}
if (makeStartupScene) {
Project_.StartupScenePath = normalizedPath;
}
SaveProjectDescriptor();
const bool refreshed = Refresh();
RescanHotReloadBaseline();
return refreshed;
}
bool MetaCoreBuiltinAssetDatabaseService::RegisterScenePath(
const std::filesystem::path& relativeScenePath,
bool makeStartupScene
@ -6227,6 +6313,11 @@ public:
MetaCoreEditorContext& editorContext,
const std::filesystem::path& relativePrefabPath
) override;
[[nodiscard]] std::optional<std::filesystem::path> CreatePrefabFromObject(
MetaCoreEditorContext& editorContext,
MetaCoreId objectId,
const std::filesystem::path& relativePrefabPath
) override;
[[nodiscard]] std::optional<MetaCoreId> InstantiatePrefab(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& prefabAssetGuid,
@ -7490,6 +7581,14 @@ std::optional<MetaCorePrefabDocument> MetaCoreBuiltinPrefabService::LoadPrefabDo
std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabFromSelection(
MetaCoreEditorContext& editorContext,
const std::filesystem::path& relativePrefabPath
) {
return CreatePrefabFromObject(editorContext, editorContext.GetActiveObjectId(), relativePrefabPath);
}
std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabFromObject(
MetaCoreEditorContext& editorContext,
MetaCoreId objectId,
const std::filesystem::path& relativePrefabPath
) {
if (AssetDatabaseService_ == nullptr ||
ReflectionRegistry_ == nullptr ||
@ -7498,19 +7597,18 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
return std::nullopt;
}
const MetaCoreId activeObjectId = editorContext.GetActiveObjectId();
if (activeObjectId == 0 || !editorContext.GetScene().FindGameObject(activeObjectId)) {
if (objectId == 0 || !editorContext.GetScene().FindGameObject(objectId)) {
return std::nullopt;
}
std::vector<MetaCoreGameObject> prefabObjects = MetaCoreCaptureSubtree(editorContext.GetScene(), activeObjectId);
std::vector<MetaCoreGameObject> prefabObjects = MetaCoreCaptureSubtree(editorContext.GetScene(), objectId);
if (prefabObjects.empty()) {
return std::nullopt;
}
MetaCoreNormalizePrefabSubtree(prefabObjects, activeObjectId);
MetaCoreNormalizePrefabSubtree(prefabObjects, objectId);
const MetaCoreGameObject rootObject = editorContext.GetScene().FindGameObject(activeObjectId);
const MetaCoreGameObject rootObject = editorContext.GetScene().FindGameObject(objectId);
const std::filesystem::path normalizedPrefabPath = relativePrefabPath.lexically_normal();
const std::filesystem::path absolutePrefabPath = AssetDatabaseService_->GetProjectDescriptor().RootPath / normalizedPrefabPath;

File diff suppressed because it is too large Load Diff

View File

@ -483,7 +483,7 @@ void MetaCoreDrawInfernuxStatusBar(MetaCoreEditorContext& editorContext, const I
MetaCoreEditorContext& editorContext,
const MetaCoreProjectAssetDragDropPayload& payload
) {
if (payload.StorageKind != MetaCoreAssetStorageKind::SourcePackage) {
if (payload.IsDirectory || !payload.AssetGuid.IsValid()) {
return false;
}
@ -525,8 +525,18 @@ void MetaCoreDrawSceneViewportDropTarget(
const ImVec2& viewportSize
) {
const ImGuiPayload* dragDropPayload = ImGui::GetDragDropPayload();
const bool projectAssetDragActive =
dragDropPayload != nullptr && dragDropPayload->IsDataType(MetaCoreProjectAssetDragDropPayloadType);
bool projectAssetDragActive =
dragDropPayload != nullptr &&
dragDropPayload->IsDataType(MetaCoreProjectAssetDragDropPayloadType) &&
dragDropPayload->DataSize == sizeof(MetaCoreProjectAssetDragDropPayload);
if (projectAssetDragActive) {
const auto* projectAssetPayload = static_cast<const MetaCoreProjectAssetDragDropPayload*>(dragDropPayload->Data);
const std::string assetType = projectAssetPayload == nullptr ? std::string{} : std::string(projectAssetPayload->AssetType);
projectAssetDragActive =
projectAssetPayload != nullptr &&
!projectAssetPayload->IsDirectory &&
(assetType == "model" || assetType == "prefab");
}
if (!projectAssetDragActive) {
return;
}
@ -1130,57 +1140,7 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
MetaCoreTraceStartup("metacore.app: initialize message handler");
#if defined(_WIN32)
DragAcceptFiles(static_cast<HWND>(Window_.GetNativeWindowHandle()), TRUE);
// 允许低权限的 Windows 资源管理器 (Explorer) 向高权限的编辑器进程发送拖放消息,彻底解决拖拽禁止图标的系统底层限制
#ifndef MSGFLT_ADD
#define MSGFLT_ADD 1
#endif
ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); // WM_COPYGLOBALMEM
Window_.SetNativeWindowMessageHandler([this](void* nativeWindowHandle, unsigned int message, std::uintptr_t wparam, std::intptr_t lparam) {
if (message == WM_DROPFILES) {
HDROP hDrop = reinterpret_cast<HDROP>(wparam);
UINT fileCount = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
const auto importPipelineService = ModuleRegistry_.ResolveService<MetaCoreIImportPipelineService>();
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject() && importPipelineService != nullptr) {
const std::filesystem::path assetsPath = assetDatabaseService->GetProjectDescriptor().AssetsPath;
// 【修复外部拖入目录问题】:如果当前编辑器选中了特定子文件夹目录(例如 Assets/Models则应当拷贝入该子目录中而不是根目录
std::filesystem::path targetFolder = assetsPath;
if (EditorContext_ != nullptr) {
const std::filesystem::path selectedDir = EditorContext_->GetSelectedProjectDirectory();
if (!selectedDir.empty()) {
const std::filesystem::path candidate = assetDatabaseService->GetProjectDescriptor().RootPath / selectedDir;
if (std::filesystem::exists(candidate)) {
targetFolder = candidate;
}
}
}
bool importedAny = false;
for (UINT i = 0; i < fileCount; ++i) {
wchar_t filePath[MAX_PATH];
if (DragQueryFileW(hDrop, i, filePath, MAX_PATH)) {
std::filesystem::path sourcePath(filePath);
std::filesystem::path targetPath = targetFolder / sourcePath.filename();
try {
// 使用 overwrite_existing 允许外部重构模型直接拖入刷新,触发引擎自动重导入并更新引用
std::filesystem::copy_file(sourcePath, targetPath, std::filesystem::copy_options::overwrite_existing);
importedAny = true;
} catch (...) { }
}
}
if (importedAny) {
(void)importPipelineService->RefreshImports();
(void)assetDatabaseService->Refresh();
}
}
DragFinish(hDrop);
return true;
}
Window_.SetNativeWindowMessageHandler([](void* nativeWindowHandle, unsigned int message, std::uintptr_t wparam, std::intptr_t lparam) {
return ImGui_ImplWin32_WndProcHandler(static_cast<HWND>(nativeWindowHandle), message, static_cast<WPARAM>(wparam), static_cast<LPARAM>(lparam)) != 0;
});
#endif

View File

@ -70,7 +70,7 @@ struct MetaCoreSelectedAssetState {
MetaCoreAssetStorageKind StorageKind = MetaCoreAssetStorageKind::SourcePackage;
[[nodiscard]] bool IsValid() const {
return Guid.IsValid();
return Guid.IsValid() || !RelativePath.empty();
}
void Clear() {
@ -87,6 +87,8 @@ struct MetaCoreProjectAssetDragDropPayload {
MetaCoreAssetGuid AssetGuid{};
MetaCoreAssetStorageKind StorageKind = MetaCoreAssetStorageKind::SourcePackage;
char AssetType[32]{};
char RelativePath[512]{};
bool IsDirectory = false;
};
inline constexpr const char* MetaCoreGeneratedMaterialDragDropPayloadType = "MC_GENERATED_MATERIAL_ASSET";

View File

@ -142,6 +142,7 @@ public:
[[nodiscard]] virtual bool OpenProject(const std::filesystem::path& projectRoot) = 0;
[[nodiscard]] virtual bool RenamePath(const std::filesystem::path& relativePath, const std::filesystem::path& newName) = 0;
[[nodiscard]] virtual bool MovePath(const std::filesystem::path& relativePath, const std::filesystem::path& targetDirectory) = 0;
[[nodiscard]] virtual bool MovePathTo(const std::filesystem::path& relativePath, const std::filesystem::path& targetRelativePath) = 0;
[[nodiscard]] virtual bool DeletePath(const std::filesystem::path& relativePath) = 0;
[[nodiscard]] virtual std::vector<std::filesystem::path> GetDirectoriesUnder(const std::filesystem::path& relativeDirectory) const = 0;
[[nodiscard]] virtual std::vector<MetaCoreAssetRecord> GetAssetsUnder(const std::filesystem::path& relativeDirectory) const = 0;
@ -149,6 +150,8 @@ public:
virtual bool ApplyExternalFileChanges(const std::vector<MetaCoreAssetChangeEvent>& changes) = 0;
virtual bool Refresh() = 0;
virtual bool CreateFolder(const std::filesystem::path& relativeDirectory) = 0;
virtual bool CreateMaterialAsset(const std::filesystem::path& relativeMaterialPath) = 0;
virtual bool CreateEmptySceneAsset(const std::filesystem::path& relativeScenePath, bool makeStartupScene) = 0;
virtual bool RegisterScenePath(const std::filesystem::path& relativeScenePath, bool makeStartupScene) = 0;
virtual bool SetStartupScenePath(const std::filesystem::path& relativeScenePath) = 0;
};
@ -280,6 +283,11 @@ public:
MetaCoreEditorContext& editorContext,
const std::filesystem::path& relativePrefabPath
) = 0;
[[nodiscard]] virtual std::optional<std::filesystem::path> CreatePrefabFromObject(
MetaCoreEditorContext& editorContext,
MetaCoreId objectId,
const std::filesystem::path& relativePrefabPath
) = 0;
[[nodiscard]] virtual std::optional<MetaCoreId> InstantiatePrefab(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& prefabAssetGuid,

View File

@ -4,16 +4,19 @@
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <shellapi.h>
#include <chrono>
#include <cstdio>
#include <ctime>
#include <exception>
#include <filesystem>
#include <string>
#include <string_view>
#include <unordered_map>
#include <memory>
#include <utility>
#include <vector>
namespace MetaCore {
@ -92,6 +95,7 @@ public:
HWND NativeHandle = nullptr;
MetaCoreInput Input{};
MetaCoreNativeWindowMessageHandler MessageHandler{};
std::vector<std::filesystem::path> DroppedFiles{};
WNDPROC OriginalWindowProc = nullptr;
std::chrono::steady_clock::time_point LastFrameTime = std::chrono::steady_clock::now();
float DeltaSeconds = 1.0F / 60.0F;
@ -107,6 +111,25 @@ LRESULT CALLBACK MetaCoreWindowSubclassProc(HWND hwnd, UINT msg, WPARAM wparam,
}
MetaCoreWindow::MetaCoreWindowImpl& owner = *iterator->second;
if (msg == WM_DROPFILES) {
HDROP dropHandle = reinterpret_cast<HDROP>(wparam);
const UINT fileCount = DragQueryFileW(dropHandle, 0xFFFFFFFF, nullptr, 0);
for (UINT index = 0; index < fileCount; ++index) {
const UINT length = DragQueryFileW(dropHandle, index, nullptr, 0);
if (length == 0) {
continue;
}
std::wstring buffer(static_cast<std::size_t>(length) + 1, L'\0');
if (DragQueryFileW(dropHandle, index, buffer.data(), length + 1) != 0) {
buffer.resize(length);
owner.DroppedFiles.emplace_back(buffer);
}
}
DragFinish(dropHandle);
return 1;
}
bool handledByEditor = false;
if (owner.MessageHandler) {
handledByEditor = owner.MessageHandler(hwnd, msg, static_cast<std::uintptr_t>(wparam), static_cast<std::intptr_t>(lparam));
@ -186,6 +209,12 @@ bool MetaCoreWindow::Initialize(int width, int height, const std::string& title)
MetaCoreWindowTrace("metacore.window: window created");
MetaCoreGetWindowMap()[Impl_->NativeHandle] = Impl_.get();
DragAcceptFiles(Impl_->NativeHandle, TRUE);
#ifndef MSGFLT_ADD
#define MSGFLT_ADD 1
#endif
ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
ChangeWindowMessageFilter(0x0049, MSGFLT_ADD);
Impl_->CloseRequested = false;
Impl_->LastFrameTime = std::chrono::steady_clock::now();
@ -306,6 +335,12 @@ std::pair<int, int> MetaCoreWindow::GetFramebufferSize() const {
return GetWindowSize();
}
std::vector<std::filesystem::path> MetaCoreWindow::ConsumeDroppedFiles() {
std::vector<std::filesystem::path> droppedFiles;
droppedFiles.swap(Impl_->DroppedFiles);
return droppedFiles;
}
void MetaCoreWindow::SetNativeWindowMessageHandler(MetaCoreNativeWindowMessageHandler handler) {
Impl_->MessageHandler = std::move(handler);
}

View File

@ -5,9 +5,11 @@
#include <chrono>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace MetaCore {
@ -59,6 +61,7 @@ public:
GLFWwindow* NativeHandle = nullptr;
MetaCoreInput Input{};
MetaCoreNativeWindowMessageHandler MessageHandler{};
std::vector<std::filesystem::path> DroppedFiles{};
std::chrono::steady_clock::time_point LastFrameTime = std::chrono::steady_clock::now();
float DeltaSeconds = 1.0F / 60.0F;
bool Initialized = false;
@ -84,6 +87,16 @@ void MetaCoreGlfwScrollCallback(GLFWwindow* window, double, double yOffset) {
}
}
void MetaCoreGlfwDropCallback(GLFWwindow* window, int count, const char** paths) {
if (auto* owner = MetaCoreGetWindowImpl(window); owner != nullptr) {
for (int index = 0; index < count; ++index) {
if (paths[index] != nullptr && paths[index][0] != '\0') {
owner->DroppedFiles.emplace_back(paths[index]);
}
}
}
}
} // namespace
MetaCoreWindow::MetaCoreWindow()
@ -118,6 +131,7 @@ bool MetaCoreWindow::Initialize(int width, int height, const std::string& title)
glfwSetWindowUserPointer(Impl_->NativeHandle, Impl_.get());
glfwSetWindowCloseCallback(Impl_->NativeHandle, MetaCoreGlfwWindowCloseCallback);
glfwSetScrollCallback(Impl_->NativeHandle, MetaCoreGlfwScrollCallback);
glfwSetDropCallback(Impl_->NativeHandle, MetaCoreGlfwDropCallback);
Impl_->CloseRequested = false;
Impl_->LastFrameTime = std::chrono::steady_clock::now();
Impl_->Initialized = true;
@ -139,6 +153,7 @@ bool MetaCoreWindow::Initialize(int width, int height, const std::string& title)
void MetaCoreWindow::Shutdown() {
if (Impl_->NativeHandle != nullptr) {
glfwSetDropCallback(Impl_->NativeHandle, nullptr);
glfwSetWindowUserPointer(Impl_->NativeHandle, nullptr);
glfwDestroyWindow(Impl_->NativeHandle);
Impl_->NativeHandle = nullptr;
@ -250,6 +265,12 @@ std::pair<int, int> MetaCoreWindow::GetFramebufferSize() const {
return {width, height};
}
std::vector<std::filesystem::path> MetaCoreWindow::ConsumeDroppedFiles() {
std::vector<std::filesystem::path> droppedFiles;
droppedFiles.swap(Impl_->DroppedFiles);
return droppedFiles;
}
void MetaCoreWindow::SetNativeWindowMessageHandler(MetaCoreNativeWindowMessageHandler handler) {
Impl_->MessageHandler = std::move(handler);
}

View File

@ -3,10 +3,12 @@
#include "MetaCorePlatform/MetaCoreInput.h"
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace MetaCore {
@ -36,6 +38,7 @@ public:
[[nodiscard]] float GetDeltaSeconds() const;
[[nodiscard]] std::pair<int, int> GetWindowSize() const;
[[nodiscard]] std::pair<int, int> GetFramebufferSize() const;
[[nodiscard]] std::vector<std::filesystem::path> ConsumeDroppedFiles();
void SetNativeWindowMessageHandler(MetaCoreNativeWindowMessageHandler handler);

View File

@ -601,6 +601,83 @@ void MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor() {
std::filesystem::remove_all(tempProjectRoot);
}
void MetaCoreTestProjectAssetDatabaseOperations() {
const std::filesystem::path tempProjectRoot =
std::filesystem::temp_directory_path() / "MetaCoreProjectAssetDatabaseOperations";
std::filesystem::remove_all(tempProjectRoot);
std::filesystem::create_directories(tempProjectRoot / "Assets");
std::filesystem::create_directories(tempProjectRoot / "Scenes");
std::filesystem::create_directories(tempProjectRoot / "Library");
{
std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc);
projectFile << "{\n"
<< " \"name\": \"ProjectAssetOps\",\n"
<< " \"version\": \"0.1.0\",\n"
<< " \"scenes\": [],\n"
<< " \"startup_scene\": \"\"\n"
<< "}\n";
}
{
std::ofstream sourceFile(tempProjectRoot / "Assets" / "Source.dat", std::ios::trunc);
sourceFile << "asset";
}
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
coreServicesModule->Startup(moduleRegistry);
const auto assetDatabase = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>();
MetaCoreExpect(assetDatabase != nullptr, "应解析到 AssetDatabaseService");
MetaCoreExpect(assetDatabase->HasProject(), "项目应已加载");
MetaCoreExpect(assetDatabase->Refresh(), "应能刷新资产数据库");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / "Assets" / "Source.dat.mcmeta"), "Refresh 应生成源资产 mcmeta");
const std::filesystem::path movedPath = std::filesystem::path("Assets") / "Moved" / "SourceMoved.dat";
MetaCoreExpect(assetDatabase->MovePathTo(std::filesystem::path("Assets") / "Source.dat", movedPath), "MovePathTo 应能精确移动文件");
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / "Assets" / "Source.dat"), "移动后旧源文件应消失");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / movedPath), "移动后目标文件应存在");
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / "Assets" / "Source.dat.mcmeta"), "移动后旧 mcmeta 应消失");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / "Assets" / "Moved" / "SourceMoved.dat.mcmeta"), "移动后新 mcmeta 应存在");
MetaCoreExpect(!assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Source.dat").has_value(), "移动后旧路径资产记录应失效");
MetaCoreExpect(assetDatabase->FindAssetByRelativePath(movedPath).has_value(), "移动后新路径资产记录应存在");
const std::filesystem::path materialPath = std::filesystem::path("Assets") / "Materials" / "PanelMaterial.mcmaterial.json";
MetaCoreExpect(assetDatabase->CreateMaterialAsset(materialPath), "应能创建材质资源");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / materialPath), "材质源文件应存在");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / (materialPath.generic_string() + ".mcmeta")), "材质 mcmeta 应存在");
const auto materialRecord = assetDatabase->FindAssetByRelativePath(materialPath);
MetaCoreExpect(materialRecord.has_value(), "材质应进入资产数据库");
MetaCoreExpect(materialRecord->Guid.IsValid(), "材质 GUID 应有效");
MetaCoreExpect(materialRecord->Type == "material", "材质资产类型应正确");
const std::filesystem::path scenePath = std::filesystem::path("Scenes") / "PanelScene.mcscene.json";
MetaCoreExpect(assetDatabase->CreateEmptySceneAsset(scenePath, true), "应能创建空场景资源");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / scenePath), "场景源文件应存在");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / (scenePath.generic_string() + ".mcmeta")), "场景 mcmeta 应存在");
MetaCoreExpect(assetDatabase->GetProjectDescriptor().StartupScenePath == scenePath, "创建场景可设置为启动场景");
MetaCoreExpect(
std::find(
assetDatabase->GetProjectDescriptor().ScenePaths.begin(),
assetDatabase->GetProjectDescriptor().ScenePaths.end(),
scenePath
) != assetDatabase->GetProjectDescriptor().ScenePaths.end(),
"创建场景后应注册到项目场景列表"
);
MetaCoreExpect(assetDatabase->DeletePath(scenePath), "应能删除场景资源");
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / scenePath), "删除后场景源文件应消失");
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / (scenePath.generic_string() + ".mcmeta")), "删除后场景 mcmeta 应消失");
MetaCoreExpect(assetDatabase->GetProjectDescriptor().StartupScenePath.empty(), "删除启动场景后 startup scene 应清空");
coreServicesModule->Shutdown(moduleRegistry);
moduleRegistry.ShutdownServices();
_putenv_s("METACORE_PROJECT_PATH", "");
std::filesystem::remove_all(tempProjectRoot);
}
void MetaCoreTestComponentRegistryDescriptors() {
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
@ -2482,6 +2559,25 @@ void MetaCoreTestPrefabWorkflow() {
MetaCoreExpect(revertedRoot, "还原后应重新选中实例根");
MetaCoreExpectVec3Near(revertedRoot.GetComponent<MetaCore::MetaCoreTransformComponent>().Position, glm::vec3(8.0F, 9.0F, 10.0F), "Revert 后实例应恢复为 prefab 内容");
MetaCore::MetaCoreGameObject draggedSource = scene.CreateGameObject("DraggedPrefabSource");
draggedSource.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(4.0F, 5.0F, 6.0F);
const std::filesystem::path draggedPrefabPath = std::filesystem::path("Assets") / "Prefabs" / "DraggedPrefabSource.mcprefab.json";
const auto draggedCreatedPath = prefabService->CreatePrefabFromObject(editorContext, draggedSource.GetId(), draggedPrefabPath);
MetaCoreExpect(draggedCreatedPath.has_value(), "应能从指定对象创建 prefab");
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / draggedPrefabPath), "指定对象 prefab 文件应存在");
MetaCoreExpect(assetDatabaseService->Refresh(), "指定对象 prefab 创建后应能刷新资产数据库");
const auto draggedPrefabRecord = assetDatabaseService->FindAssetByRelativePath(draggedPrefabPath);
MetaCoreExpect(draggedPrefabRecord.has_value(), "指定对象 prefab 应进入资产数据库");
const auto draggedPrefabDocument = MetaCore::MetaCoreSceneSerializer::LoadPrefabFromJson(tempProjectRoot / draggedPrefabPath, reflectionRegistry->GetTypeRegistry());
MetaCoreExpect(draggedPrefabDocument.has_value(), "指定对象 prefab 应能被 JSON 解析");
MetaCoreExpect(!draggedPrefabDocument->GameObjects.empty(), "指定对象 prefab 应包含对象数据");
MetaCoreExpect(draggedPrefabDocument->GameObjects.front().Name == "DraggedPrefabSource", "指定对象 prefab 应使用目标对象数据");
MetaCoreExpectVec3Near(
draggedPrefabDocument->GameObjects.front().Transform.Position,
glm::vec3(4.0F, 5.0F, 6.0F),
"指定对象 prefab 应保留目标对象 transform"
);
coreServicesModule->Shutdown(moduleRegistry);
moduleRegistry.ShutdownServices();
_putenv_s("METACORE_PROJECT_PATH", "");
@ -4217,6 +4313,8 @@ int main() {
MetaCoreTestAssetDatabaseRenamePathUpdatesProjectDescriptor();
std::cout << "[RUN] MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor..." << std::endl;
MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor();
std::cout << "[RUN] MetaCoreTestProjectAssetDatabaseOperations..." << std::endl;
MetaCoreTestProjectAssetDatabaseOperations();
std::cout << "[RUN] MetaCoreTestComponentRegistryDescriptors..." << std::endl;
MetaCoreTestComponentRegistryDescriptors();
std::cout << "[RUN] MetaCoreTestSceneEditingServiceCreateCamera..." << std::endl;
@ -4249,6 +4347,8 @@ int main() {
MetaCoreTestInstantiateImportedModelAssetIntoScene();
std::cout << "[RUN] MetaCoreTestInstantiateMultiNodeModelAssetIntoScene..." << std::endl;
MetaCoreTestInstantiateMultiNodeModelAssetIntoScene();
std::cout << "[RUN] MetaCoreTestPrefabWorkflow..." << std::endl;
MetaCoreTestPrefabWorkflow();
std::cout << "[RUN] MetaCoreTestDeleteModelAssetCorrectlyClearsFromViewport..." << std::endl;
MetaCoreTestDeleteModelAssetCorrectlyClearsFromViewport();
std::cout << "[RUN] MetaCoreTestDeleteModelSubChildCorrectlyClearsFromViewport..." << std::endl;
@ -4257,8 +4357,6 @@ int main() {
MetaCoreTestModelReimportKeepsGeneratedAssetReferencesStable();
std::cout << "[RUN] MetaCoreTestBootstrapStartupSceneCreation..." << std::endl;
MetaCoreTestBootstrapStartupSceneCreation();
std::cout << "[RUN] MetaCoreTestPrefabWorkflow..." << std::endl;
MetaCoreTestPrefabWorkflow();
std::cout << "[RUN] MetaCoreTestCookPipelineCooksJsonScenePrefabMaterialUi..." << std::endl;
MetaCoreTestCookPipelineCooksJsonScenePrefabMaterialUi();
std::cout << "[RUN] MetaCoreTestComponentRegistryOperations..." << std::endl;