resources: formalize internal shader ir
This commit is contained in:
@@ -387,6 +387,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Material/MaterialLoader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Shader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/ShaderLoader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/ShaderIR.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/ShaderSourceUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/ShaderSourceUtils.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/ShaderAuthoringParser.h
|
||||
|
||||
@@ -433,10 +433,10 @@ bool TryParseUnityStyleBlendDirective(
|
||||
}
|
||||
|
||||
void SetOrReplaceAuthoringTag(
|
||||
std::vector<AuthoringTagEntry>& tags,
|
||||
std::vector<ShaderTagIR>& tags,
|
||||
const Containers::String& name,
|
||||
const Containers::String& value) {
|
||||
for (AuthoringTagEntry& tag : tags) {
|
||||
for (ShaderTagIR& tag : tags) {
|
||||
if (tag.name == name) {
|
||||
tag.value = value;
|
||||
return;
|
||||
@@ -926,7 +926,7 @@ bool TryTokenizeQuotedArguments(const std::string& line, std::vector<std::string
|
||||
|
||||
bool TryParseInlineTagAssignments(
|
||||
const std::string& line,
|
||||
std::vector<AuthoringTagEntry>& outTags) {
|
||||
std::vector<ShaderTagIR>& outTags) {
|
||||
const size_t openBrace = line.find('{');
|
||||
const size_t closeBrace = line.rfind('}');
|
||||
if (openBrace == std::string::npos ||
|
||||
@@ -1117,7 +1117,7 @@ bool TryParseAuthoringResourceLine(
|
||||
bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
AuthoringShaderDesc& outDesc,
|
||||
ShaderIR& outDesc,
|
||||
Containers::String* outError) {
|
||||
(void)path;
|
||||
outDesc = {};
|
||||
@@ -1147,8 +1147,8 @@ bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
|
||||
std::vector<BlockKind> blockStack;
|
||||
BlockKind pendingBlock = BlockKind::None;
|
||||
AuthoringSubShaderEntry* currentSubShader = nullptr;
|
||||
AuthoringPassEntry* currentPass = nullptr;
|
||||
ShaderSubShaderIR* currentSubShader = nullptr;
|
||||
ShaderPassIR* currentPass = nullptr;
|
||||
bool inProgram = false;
|
||||
|
||||
auto currentBlock = [&blockStack]() -> BlockKind {
|
||||
@@ -1183,7 +1183,7 @@ bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
continue;
|
||||
}
|
||||
if (pragmaTokens.size() >= 6u && pragmaTokens[1] == "backend") {
|
||||
AuthoringBackendVariantEntry backendVariant = {};
|
||||
ShaderBackendVariantIR backendVariant = {};
|
||||
if (!TryParseShaderBackend(pragmaTokens[2].c_str(), backendVariant.backend)) {
|
||||
return fail("invalid backend pragma backend name", humanLine);
|
||||
}
|
||||
@@ -1310,7 +1310,7 @@ bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
}
|
||||
|
||||
if (StartsWithKeyword(line, "Tags")) {
|
||||
std::vector<AuthoringTagEntry> parsedTags;
|
||||
std::vector<ShaderTagIR> parsedTags;
|
||||
if (!TryParseInlineTagAssignments(line, parsedTags)) {
|
||||
return fail("Tags block must use inline key/value pairs", humanLine);
|
||||
}
|
||||
@@ -1375,12 +1375,12 @@ bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
return fail("shader does not declare any SubShader blocks", 0);
|
||||
}
|
||||
|
||||
for (const AuthoringSubShaderEntry& subShader : outDesc.subShaders) {
|
||||
for (const ShaderSubShaderIR& subShader : outDesc.subShaders) {
|
||||
if (subShader.passes.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const AuthoringPassEntry& pass : subShader.passes) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
if (pass.name.Empty()) {
|
||||
return fail("a Pass is missing a Name directive", 0);
|
||||
}
|
||||
@@ -1396,7 +1396,7 @@ bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
bool ParseUnityStyleSingleSourceShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
AuthoringShaderDesc& outDesc,
|
||||
ShaderIR& outDesc,
|
||||
Containers::String* outError) {
|
||||
(void)path;
|
||||
outDesc = {};
|
||||
@@ -1435,8 +1435,8 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
|
||||
size_t nextExtractedBlock = 0;
|
||||
std::vector<BlockKind> blockStack;
|
||||
BlockKind pendingBlock = BlockKind::None;
|
||||
AuthoringSubShaderEntry* currentSubShader = nullptr;
|
||||
AuthoringPassEntry* currentPass = nullptr;
|
||||
ShaderSubShaderIR* currentSubShader = nullptr;
|
||||
ShaderPassIR* currentPass = nullptr;
|
||||
bool inProgramBlock = false;
|
||||
bool inSharedIncludeBlock = false;
|
||||
|
||||
@@ -1611,7 +1611,7 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
|
||||
}
|
||||
|
||||
if (StartsWithKeyword(line, "Tags")) {
|
||||
std::vector<AuthoringTagEntry> parsedTags;
|
||||
std::vector<ShaderTagIR> parsedTags;
|
||||
if (!TryParseInlineTagAssignments(line, parsedTags)) {
|
||||
return fail("Tags block must use inline key/value pairs", humanLine);
|
||||
}
|
||||
@@ -1886,12 +1886,12 @@ bool ParseUnityStyleSingleSourceShaderAuthoring(
|
||||
return fail("shader does not declare any SubShader blocks", 0);
|
||||
}
|
||||
|
||||
for (AuthoringSubShaderEntry& subShader : outDesc.subShaders) {
|
||||
for (ShaderSubShaderIR& subShader : outDesc.subShaders) {
|
||||
if (subShader.passes.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (AuthoringPassEntry& pass : subShader.passes) {
|
||||
for (ShaderPassIR& pass : subShader.passes) {
|
||||
if (pass.name.Empty()) {
|
||||
return fail("a Pass is missing a Name directive", 0);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include "ShaderIR.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -17,51 +15,6 @@ enum class ShaderAuthoringStyle {
|
||||
UnityStyleSingleSource
|
||||
};
|
||||
|
||||
struct AuthoringTagEntry {
|
||||
Containers::String name;
|
||||
Containers::String value;
|
||||
};
|
||||
|
||||
struct AuthoringBackendVariantEntry {
|
||||
ShaderBackend backend = ShaderBackend::Generic;
|
||||
ShaderLanguage language = ShaderLanguage::GLSL;
|
||||
Containers::String vertexSourcePath;
|
||||
Containers::String fragmentSourcePath;
|
||||
Containers::String vertexProfile;
|
||||
Containers::String fragmentProfile;
|
||||
};
|
||||
|
||||
struct AuthoringPassEntry {
|
||||
Containers::String name;
|
||||
bool hasFixedFunctionState = false;
|
||||
MaterialRenderState fixedFunctionState = {};
|
||||
std::vector<AuthoringTagEntry> tags;
|
||||
Containers::Array<ShaderResourceBindingDesc> resources;
|
||||
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
|
||||
Containers::String vertexEntryPoint;
|
||||
Containers::String fragmentEntryPoint;
|
||||
Containers::String sharedProgramSource;
|
||||
Containers::String programSource;
|
||||
Containers::String targetProfile;
|
||||
std::vector<AuthoringBackendVariantEntry> backendVariants;
|
||||
};
|
||||
|
||||
struct AuthoringSubShaderEntry {
|
||||
bool hasFixedFunctionState = false;
|
||||
MaterialRenderState fixedFunctionState = {};
|
||||
std::vector<AuthoringTagEntry> tags;
|
||||
Containers::String sharedProgramSource;
|
||||
std::vector<AuthoringPassEntry> passes;
|
||||
};
|
||||
|
||||
struct AuthoringShaderDesc {
|
||||
Containers::String name;
|
||||
Containers::String fallback;
|
||||
Containers::String sharedProgramSource;
|
||||
Containers::Array<ShaderPropertyDesc> properties;
|
||||
std::vector<AuthoringSubShaderEntry> subShaders;
|
||||
};
|
||||
|
||||
void AppendAuthoringSourceBlock(
|
||||
Containers::String& target,
|
||||
const Containers::String& sourceBlock);
|
||||
@@ -86,14 +39,14 @@ ShaderAuthoringStyle DetectShaderAuthoringStyle(const std::string& sourceText);
|
||||
bool ParseLegacyBackendSplitShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
AuthoringShaderDesc& outDesc,
|
||||
ShaderIR& outDesc,
|
||||
Containers::String* outError);
|
||||
|
||||
bool ParseUnityStyleSingleSourceShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
AuthoringShaderDesc& outDesc,
|
||||
ShaderIR& outDesc,
|
||||
Containers::String* outError);
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
58
engine/src/Resources/Shader/ShaderIR.h
Normal file
58
engine/src/Resources/Shader/ShaderIR.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
struct ShaderTagIR {
|
||||
Containers::String name;
|
||||
Containers::String value;
|
||||
};
|
||||
|
||||
struct ShaderBackendVariantIR {
|
||||
ShaderBackend backend = ShaderBackend::Generic;
|
||||
ShaderLanguage language = ShaderLanguage::GLSL;
|
||||
Containers::String vertexSourcePath;
|
||||
Containers::String fragmentSourcePath;
|
||||
Containers::String vertexProfile;
|
||||
Containers::String fragmentProfile;
|
||||
};
|
||||
|
||||
struct ShaderPassIR {
|
||||
Containers::String name;
|
||||
bool hasFixedFunctionState = false;
|
||||
MaterialRenderState fixedFunctionState = {};
|
||||
std::vector<ShaderTagIR> tags;
|
||||
Containers::Array<ShaderResourceBindingDesc> resources;
|
||||
Containers::Array<ShaderKeywordDeclaration> keywordDeclarations;
|
||||
Containers::String vertexEntryPoint;
|
||||
Containers::String fragmentEntryPoint;
|
||||
Containers::String sharedProgramSource;
|
||||
Containers::String programSource;
|
||||
Containers::String targetProfile;
|
||||
std::vector<ShaderBackendVariantIR> backendVariants;
|
||||
};
|
||||
|
||||
struct ShaderSubShaderIR {
|
||||
bool hasFixedFunctionState = false;
|
||||
MaterialRenderState fixedFunctionState = {};
|
||||
std::vector<ShaderTagIR> tags;
|
||||
Containers::String sharedProgramSource;
|
||||
std::vector<ShaderPassIR> passes;
|
||||
};
|
||||
|
||||
struct ShaderIR {
|
||||
Containers::String name;
|
||||
Containers::String fallback;
|
||||
Containers::String sharedProgramSource;
|
||||
Containers::Array<ShaderPropertyDesc> properties;
|
||||
std::vector<ShaderSubShaderIR> subShaders;
|
||||
};
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -395,33 +395,33 @@ bool ReadTextFile(const Containers::String& path, Containers::String& outText) {
|
||||
|
||||
size_t CalculateShaderMemorySize(const Shader& shader);
|
||||
|
||||
LoadResult BuildShaderFromAuthoringDesc(
|
||||
LoadResult BuildShaderFromIR(
|
||||
const Containers::String& path,
|
||||
const AuthoringShaderDesc& authoringDesc) {
|
||||
const ShaderIR& shaderIR) {
|
||||
auto shader = std::make_unique<Shader>();
|
||||
IResource::ConstructParams params;
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.name = authoringDesc.name;
|
||||
params.name = shaderIR.name;
|
||||
shader->Initialize(params);
|
||||
shader->SetFallback(authoringDesc.fallback);
|
||||
shader->SetFallback(shaderIR.fallback);
|
||||
|
||||
for (const ShaderPropertyDesc& property : authoringDesc.properties) {
|
||||
for (const ShaderPropertyDesc& property : shaderIR.properties) {
|
||||
shader->AddProperty(property);
|
||||
}
|
||||
|
||||
for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) {
|
||||
for (const AuthoringPassEntry& pass : subShader.passes) {
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
ShaderPass shaderPass = {};
|
||||
shaderPass.name = pass.name;
|
||||
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
|
||||
shaderPass.fixedFunctionState = pass.fixedFunctionState;
|
||||
shader->AddPass(shaderPass);
|
||||
|
||||
for (const AuthoringTagEntry& subShaderTag : subShader.tags) {
|
||||
for (const ShaderTagIR& subShaderTag : subShader.tags) {
|
||||
shader->SetPassTag(pass.name, subShaderTag.name, subShaderTag.value);
|
||||
}
|
||||
for (const AuthoringTagEntry& passTag : pass.tags) {
|
||||
for (const ShaderTagIR& passTag : pass.tags) {
|
||||
shader->SetPassTag(pass.name, passTag.name, passTag.value);
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ LoadResult BuildShaderFromAuthoringDesc(
|
||||
if (!pass.backendVariants.empty()) {
|
||||
const std::vector<ShaderKeywordSet> keywordSets =
|
||||
BuildShaderKeywordVariantSets(pass.keywordDeclarations);
|
||||
for (const AuthoringBackendVariantEntry& backendVariant : pass.backendVariants) {
|
||||
for (const ShaderBackendVariantIR& backendVariant : pass.backendVariants) {
|
||||
Containers::String vertexSourceCode;
|
||||
ShaderStageVariant vertexVariant = {};
|
||||
vertexVariant.stage = ShaderType::Vertex;
|
||||
@@ -486,7 +486,7 @@ LoadResult BuildShaderFromAuthoringDesc(
|
||||
}
|
||||
} else if (!pass.programSource.Empty()) {
|
||||
Containers::String combinedSource;
|
||||
AppendAuthoringSourceBlock(combinedSource, authoringDesc.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, shaderIR.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
|
||||
@@ -549,16 +549,16 @@ bool CollectLegacyBackendSplitShaderDependencyPaths(
|
||||
Containers::Array<Containers::String>& outDependencies) {
|
||||
outDependencies.Clear();
|
||||
|
||||
AuthoringShaderDesc authoringDesc = {};
|
||||
ShaderIR shaderIR = {};
|
||||
Containers::String parseError;
|
||||
if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, authoringDesc, &parseError)) {
|
||||
if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, shaderIR, &parseError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seenPaths;
|
||||
for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) {
|
||||
for (const AuthoringPassEntry& pass : subShader.passes) {
|
||||
for (const AuthoringBackendVariantEntry& backendVariant : pass.backendVariants) {
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
for (const ShaderBackendVariantIR& backendVariant : pass.backendVariants) {
|
||||
const Containers::String resolvedVertexPath =
|
||||
ResolveShaderDependencyPath(backendVariant.vertexSourcePath, path);
|
||||
const std::string vertexKey = ToStdString(resolvedVertexPath);
|
||||
@@ -585,17 +585,17 @@ bool CollectUnityStyleSingleSourceShaderDependencyPaths(
|
||||
Containers::Array<Containers::String>& outDependencies) {
|
||||
outDependencies.Clear();
|
||||
|
||||
AuthoringShaderDesc authoringDesc = {};
|
||||
ShaderIR shaderIR = {};
|
||||
Containers::String parseError;
|
||||
if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, authoringDesc, &parseError)) {
|
||||
if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, shaderIR, &parseError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seenPaths;
|
||||
CollectQuotedIncludeDependencyPaths(path, authoringDesc.sharedProgramSource, seenPaths, outDependencies);
|
||||
for (const AuthoringSubShaderEntry& subShader : authoringDesc.subShaders) {
|
||||
CollectQuotedIncludeDependencyPaths(path, shaderIR.sharedProgramSource, seenPaths, outDependencies);
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
CollectQuotedIncludeDependencyPaths(path, subShader.sharedProgramSource, seenPaths, outDependencies);
|
||||
for (const AuthoringPassEntry& pass : subShader.passes) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
CollectQuotedIncludeDependencyPaths(path, pass.sharedProgramSource, seenPaths, outDependencies);
|
||||
CollectQuotedIncludeDependencyPaths(path, pass.programSource, seenPaths, outDependencies);
|
||||
}
|
||||
@@ -607,25 +607,25 @@ bool CollectUnityStyleSingleSourceShaderDependencyPaths(
|
||||
LoadResult LoadLegacyBackendSplitShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText) {
|
||||
AuthoringShaderDesc authoringDesc = {};
|
||||
ShaderIR shaderIR = {};
|
||||
Containers::String parseError;
|
||||
if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, authoringDesc, &parseError)) {
|
||||
if (!ParseLegacyBackendSplitShaderAuthoring(path, sourceText, shaderIR, &parseError)) {
|
||||
return LoadResult(parseError);
|
||||
}
|
||||
|
||||
return BuildShaderFromAuthoringDesc(path, authoringDesc);
|
||||
return BuildShaderFromIR(path, shaderIR);
|
||||
}
|
||||
|
||||
LoadResult LoadUnityStyleSingleSourceShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText) {
|
||||
AuthoringShaderDesc authoringDesc = {};
|
||||
ShaderIR shaderIR = {};
|
||||
Containers::String parseError;
|
||||
if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, authoringDesc, &parseError)) {
|
||||
if (!ParseUnityStyleSingleSourceShaderAuthoring(path, sourceText, shaderIR, &parseError)) {
|
||||
return LoadResult(parseError);
|
||||
}
|
||||
|
||||
return BuildShaderFromAuthoringDesc(path, authoringDesc);
|
||||
return BuildShaderFromIR(path, shaderIR);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Core/UIEditorShellCompose.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
@@ -22,6 +23,7 @@ struct UIEditorShellInteractionModel {
|
||||
struct UIEditorShellInteractionState {
|
||||
UIEditorShellComposeState composeState = {};
|
||||
UIEditorMenuSession menuSession = {};
|
||||
UIEditorWorkspaceInteractionState workspaceInteractionState = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool focused = false;
|
||||
bool hasPointerPosition = false;
|
||||
@@ -82,12 +84,18 @@ struct UIEditorShellInteractionRequest {
|
||||
|
||||
struct UIEditorShellInteractionResult {
|
||||
bool consumed = false;
|
||||
bool requestPointerCapture = false;
|
||||
bool releasePointerCapture = false;
|
||||
bool viewportInteractionChanged = false;
|
||||
bool commandTriggered = false;
|
||||
std::string menuId = {};
|
||||
std::string popupId = {};
|
||||
std::string itemId = {};
|
||||
std::string commandId = {};
|
||||
std::string viewportPanelId = {};
|
||||
UIEditorViewportInputBridgeFrame viewportInputFrame = {};
|
||||
UIEditorMenuSessionMutationResult menuMutation = {};
|
||||
UIEditorWorkspaceInteractionResult workspaceResult = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionPopupFrame {
|
||||
@@ -98,6 +106,7 @@ struct UIEditorShellInteractionPopupFrame {
|
||||
struct UIEditorShellInteractionFrame {
|
||||
UIEditorShellInteractionRequest request = {};
|
||||
UIEditorShellComposeFrame shellFrame = {};
|
||||
UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = {};
|
||||
std::vector<UIEditorShellInteractionPopupFrame> popupFrames = {};
|
||||
UIEditorShellInteractionResult result = {};
|
||||
std::string openRootMenuId = {};
|
||||
@@ -109,23 +118,17 @@ struct UIEditorShellInteractionFrame {
|
||||
|
||||
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorWorkspaceController& controller,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState = {},
|
||||
const UIEditorShellInteractionState& state = {},
|
||||
const UIEditorShellInteractionMetrics& metrics = {});
|
||||
|
||||
UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
UIEditorShellInteractionState& state,
|
||||
UIEditorWorkspaceController& controller,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorDockHostState& dockHostState = {},
|
||||
const UIEditorShellInteractionMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorShellInteraction(
|
||||
|
||||
@@ -217,8 +217,11 @@ std::size_t FindPopupItemIndex(
|
||||
bool HasMeaningfulInteractionResult(
|
||||
const UIEditorShellInteractionResult& result) {
|
||||
return result.consumed ||
|
||||
result.requestPointerCapture ||
|
||||
result.releasePointerCapture ||
|
||||
result.commandTriggered ||
|
||||
result.menuMutation.changed ||
|
||||
result.workspaceResult.consumed ||
|
||||
!result.menuId.empty() ||
|
||||
!result.popupId.empty() ||
|
||||
!result.itemId.empty() ||
|
||||
@@ -227,11 +230,8 @@ bool HasMeaningfulInteractionResult(
|
||||
|
||||
BuildRequestOutput BuildRequest(
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorWorkspaceController& controller,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
BuildRequestOutput output = {};
|
||||
@@ -242,11 +242,11 @@ BuildRequestOutput BuildRequest(
|
||||
BuildShellComposeModel(model, request.menuBarItems);
|
||||
request.shellRequest = ResolveUIEditorShellComposeRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
controller.GetPanelRegistry(),
|
||||
controller.GetWorkspace(),
|
||||
controller.GetSession(),
|
||||
shellModel,
|
||||
dockHostState,
|
||||
state.workspaceInteractionState.dockHostInteractionState.dockHostState,
|
||||
state.composeState,
|
||||
metrics.shellMetrics);
|
||||
|
||||
@@ -444,7 +444,7 @@ bool ShouldUsePointerPosition(const UIInputEvent& event) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterComposeInputEvents(
|
||||
std::vector<UIInputEvent> FilterWorkspaceInputEvents(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool menuModalDuringFrame) {
|
||||
if (!menuModalDuringFrame) {
|
||||
@@ -466,44 +466,32 @@ std::vector<UIInputEvent> FilterComposeInputEvents(
|
||||
|
||||
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorWorkspaceController& controller,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
return BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
controller,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics).request;
|
||||
}
|
||||
|
||||
UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
UIEditorShellInteractionState& state,
|
||||
UIEditorWorkspaceController& controller,
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
UIEditorShellInteractionResult interactionResult = {};
|
||||
bool menuModalDuringFrame = state.menuSession.HasOpenMenu();
|
||||
|
||||
BuildRequestOutput requestBuild = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
controller,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics);
|
||||
UIEditorShellInteractionRequest request = std::move(requestBuild.request);
|
||||
@@ -516,11 +504,8 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
|
||||
requestBuild = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
controller,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics);
|
||||
request = std::move(requestBuild.request);
|
||||
@@ -664,43 +649,48 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
menuModalDuringFrame = true;
|
||||
request = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
controller,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics).request;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<UIInputEvent> workspaceInputEvents =
|
||||
FilterWorkspaceInputEvents(inputEvents, menuModalDuringFrame);
|
||||
UIEditorWorkspaceInteractionModel workspaceModel = {};
|
||||
workspaceModel.workspacePresentations = model.workspacePresentations;
|
||||
UIEditorWorkspaceInteractionFrame workspaceInteractionFrame =
|
||||
UpdateUIEditorWorkspaceInteraction(
|
||||
state.workspaceInteractionState,
|
||||
controller,
|
||||
request.shellRequest.layout.workspaceRect,
|
||||
workspaceModel,
|
||||
workspaceInputEvents,
|
||||
metrics.shellMetrics.dockHostMetrics);
|
||||
state.composeState.workspaceState = state.workspaceInteractionState.composeState;
|
||||
|
||||
request = BuildRequest(
|
||||
bounds,
|
||||
controller,
|
||||
model,
|
||||
state,
|
||||
metrics).request;
|
||||
|
||||
const RequestHit finalHit =
|
||||
HitTestRequest(request, state.pointerPosition, state.hasPointerPosition);
|
||||
UpdateMenuBarVisualState(state, request, finalHit);
|
||||
|
||||
const UIEditorShellComposeModel shellModel =
|
||||
BuildShellComposeModel(model, request.menuBarItems);
|
||||
const std::vector<UIInputEvent> composeInputEvents =
|
||||
FilterComposeInputEvents(inputEvents, menuModalDuringFrame);
|
||||
|
||||
UIEditorShellInteractionFrame frame = {};
|
||||
frame.request = request;
|
||||
frame.shellFrame = UpdateUIEditorShellCompose(
|
||||
state.composeState,
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
shellModel,
|
||||
composeInputEvents,
|
||||
dockHostState,
|
||||
metrics.shellMetrics);
|
||||
frame.shellFrame.layout = request.shellRequest.layout;
|
||||
frame.shellFrame.workspaceFrame = workspaceInteractionFrame.composeFrame;
|
||||
frame.workspaceInteractionFrame = std::move(workspaceInteractionFrame);
|
||||
frame.popupFrames = BuildPopupFrames(
|
||||
frame.request,
|
||||
state,
|
||||
finalHit.popupRequest != nullptr ? finalHit.popupRequest->popupId : std::string_view(),
|
||||
finalHit.popupItem != nullptr ? finalHit.popupItem->itemId : std::string_view());
|
||||
frame.result = interactionResult;
|
||||
frame.openRootMenuId = std::string(state.menuSession.GetOpenRootMenuId());
|
||||
frame.hoveredMenuId =
|
||||
finalHit.menuButton != nullptr ? finalHit.menuButton->menuId : std::string();
|
||||
@@ -709,6 +699,20 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
frame.hoveredItemId =
|
||||
finalHit.popupItem != nullptr ? finalHit.popupItem->itemId : std::string();
|
||||
frame.focused = state.focused || state.menuSession.HasOpenMenu();
|
||||
interactionResult.workspaceResult = frame.workspaceInteractionFrame.result;
|
||||
interactionResult.requestPointerCapture =
|
||||
interactionResult.workspaceResult.requestPointerCapture;
|
||||
interactionResult.releasePointerCapture =
|
||||
interactionResult.workspaceResult.releasePointerCapture;
|
||||
interactionResult.viewportInteractionChanged =
|
||||
interactionResult.workspaceResult.viewportInteractionChanged;
|
||||
interactionResult.viewportPanelId =
|
||||
interactionResult.workspaceResult.viewportPanelId;
|
||||
interactionResult.viewportInputFrame =
|
||||
interactionResult.workspaceResult.viewportInputFrame;
|
||||
interactionResult.consumed =
|
||||
interactionResult.consumed || interactionResult.workspaceResult.consumed;
|
||||
frame.result = std::move(interactionResult);
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ Scenarios:
|
||||
- `editor.shell.editor_shell_interaction`
|
||||
Build target: `editor_ui_editor_shell_interaction_validation`
|
||||
Executable: `XCUIEditorShellInteractionValidation.exe`
|
||||
Scope: root shell interaction only; menu bar root switching, submenu hover chain, outside/Esc dismiss, command hook, and workspace input shielding
|
||||
Scope: root shell unified interaction only; menu bar root switching, submenu hover chain, outside/Esc dismiss, command hook, menu-modal workspace shielding, and post-dismiss workspace interaction restore
|
||||
|
||||
- `editor.shell.dock_host_basic`
|
||||
Build target: `editor_ui_dock_host_basic_validation`
|
||||
@@ -139,7 +139,7 @@ Selected controls:
|
||||
Click `切到 Scene / 切到 Document`, toggle `TopBar / BottomBar / Texture`, inspect `MenuBar Rect / Workspace Rect / StatusBar Rect / Selected Presentation / Request Size`, press `Reset`, press `截图` or `F12`.
|
||||
|
||||
- `shell/editor_shell_interaction/`
|
||||
Click `File / Window`, hover `Workspace Tools`, click outside the menu or press `Esc`, trigger a menu command, inspect `Open Root / Popup Chain / Submenu Path / Result / Active Panel / Visible Panels`, press `Reset`, `Capture`, or `F12`.
|
||||
Click `File / Window`, hover `Workspace Tools`, click outside the menu or press `Esc`, then click `Document` or drag a splitter, inspect `Open Root / Popup Chain / Submenu Path / Selected Presentation / Active Panel / Host Capture / Result`, press `Reset`, `Capture`, or `F12`.
|
||||
|
||||
- `shell/dock_host_basic/`
|
||||
Drag `root-split`, click `Document A`, close `Document B`, click `Details`, close `Console`, inspect `Hover / Result / Active Panel / Visible Panels / Capture / split ratio`, press `Reset`, `Capture`, or `F12`.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
|
||||
using XCEngine::UI::Editor::GetUIEditorCommandDispatchStatusName;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorShellInteraction;
|
||||
using XCEngine::UI::Editor::UIEditorCommandDispatchResult;
|
||||
@@ -63,6 +64,7 @@ using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionModel;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionResult;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionState;
|
||||
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
@@ -120,11 +122,15 @@ bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
|
||||
bool HasMeaningfulInteractionResult(const UIEditorShellInteractionResult& result) {
|
||||
return result.consumed ||
|
||||
result.requestPointerCapture ||
|
||||
result.releasePointerCapture ||
|
||||
result.commandTriggered ||
|
||||
result.menuMutation.changed ||
|
||||
result.workspaceResult.consumed ||
|
||||
!result.menuId.empty() ||
|
||||
!result.popupId.empty() ||
|
||||
!result.itemId.empty() ||
|
||||
!result.viewportPanelId.empty() ||
|
||||
!result.commandId.empty();
|
||||
}
|
||||
|
||||
@@ -149,6 +155,34 @@ std::string FormatDismissReason(UIPopupDismissReason reason) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) {
|
||||
if (frame.captureStarted) {
|
||||
return "Viewport CaptureStarted";
|
||||
}
|
||||
if (frame.captureEnded) {
|
||||
return "Viewport CaptureEnded";
|
||||
}
|
||||
if (frame.focusGained) {
|
||||
return "Viewport FocusGained";
|
||||
}
|
||||
if (frame.focusLost) {
|
||||
return "Viewport FocusLost";
|
||||
}
|
||||
if (frame.pointerPressedInside) {
|
||||
return "Viewport PointerDownInside";
|
||||
}
|
||||
if (frame.pointerReleasedInside) {
|
||||
return "Viewport PointerUpInside";
|
||||
}
|
||||
if (frame.pointerMoved) {
|
||||
return "Viewport PointerMove";
|
||||
}
|
||||
if (frame.wheelDelta != 0.0f) {
|
||||
return "Viewport Wheel";
|
||||
}
|
||||
return "Viewport Input";
|
||||
}
|
||||
|
||||
std::string JoinVisiblePanelIds(
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session) {
|
||||
@@ -357,8 +391,11 @@ private:
|
||||
void UpdateLayout();
|
||||
void HandleMouseMove(float x, float y);
|
||||
void HandleLeftButtonDown(float x, float y);
|
||||
void HandleLeftButtonUp(float x, float y);
|
||||
void ExecuteAction(ActionId action);
|
||||
UIEditorShellInteractionModel BuildInteractionModel() const;
|
||||
bool HasInteractiveCaptureState() const;
|
||||
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
|
||||
void SetInteractionResult(const UIEditorShellInteractionResult& result);
|
||||
void SetDispatchResult(const UIEditorCommandDispatchResult& result);
|
||||
void RenderFrame();
|
||||
@@ -464,6 +501,12 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
if (app != nullptr) {
|
||||
app->HandleLeftButtonUp(static_cast<float>(GET_X_LPARAM(lParam)), static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_SETFOCUS:
|
||||
if (app != nullptr) {
|
||||
UIInputEvent event = {};
|
||||
@@ -480,6 +523,16 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_CAPTURECHANGED:
|
||||
if (app != nullptr &&
|
||||
reinterpret_cast<HWND>(lParam) != hwnd &&
|
||||
app->HasInteractiveCaptureState()) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::FocusLost;
|
||||
app->m_pendingInputEvents.push_back(event);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
if (app != nullptr) {
|
||||
@@ -553,6 +606,9 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
}
|
||||
|
||||
void ScenarioApp::Shutdown() {
|
||||
if (GetCapture() == m_hwnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
m_autoScreenshot.Shutdown();
|
||||
m_renderer.Shutdown();
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
@@ -564,6 +620,9 @@ void ScenarioApp::Shutdown() {
|
||||
}
|
||||
|
||||
void ScenarioApp::ResetScenario() {
|
||||
if (GetCapture() == m_hwnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
m_commandDispatcher = UIEditorCommandDispatcher(BuildCommandRegistry());
|
||||
m_menuModel = BuildMenuModel();
|
||||
@@ -627,6 +686,14 @@ void ScenarioApp::HandleLeftButtonDown(float x, float y) {
|
||||
m_pendingInputEvents.push_back(event);
|
||||
}
|
||||
|
||||
void ScenarioApp::HandleLeftButtonUp(float x, float y) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::PointerButtonUp;
|
||||
event.position = UIPoint(x, y);
|
||||
event.pointerButton = UIPointerButton::Left;
|
||||
m_pendingInputEvents.push_back(event);
|
||||
}
|
||||
|
||||
void ScenarioApp::ExecuteAction(ActionId action) {
|
||||
if (action == ActionId::Reset) {
|
||||
ResetScenario();
|
||||
@@ -642,6 +709,29 @@ void ScenarioApp::ExecuteAction(ActionId action) {
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
|
||||
bool ScenarioApp::HasInteractiveCaptureState() const {
|
||||
if (m_interactionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& panelState : m_interactionState.workspaceInteractionState.composeState.panelStates) {
|
||||
if (panelState.viewportShellState.inputBridgeState.captured) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScenarioApp::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) {
|
||||
if (result.requestPointerCapture && GetCapture() != m_hwnd) {
|
||||
SetCapture(m_hwnd);
|
||||
}
|
||||
if (result.releasePointerCapture && GetCapture() == m_hwnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
UIEditorShellInteractionModel ScenarioApp::BuildInteractionModel() const {
|
||||
UIEditorShellInteractionModel model = {};
|
||||
model.resolvedMenuModel = BuildUIEditorResolvedMenuModel(
|
||||
@@ -720,6 +810,46 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.layoutResult.status !=
|
||||
XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus::Rejected) {
|
||||
m_lastStatus = "WorkspaceLayout";
|
||||
m_lastMessage = result.workspaceResult.dockHostResult.layoutResult.message;
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.commandResult.status !=
|
||||
XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus::Rejected) {
|
||||
m_lastStatus = "WorkspaceCommand";
|
||||
m_lastMessage = result.workspaceResult.dockHostResult.commandResult.message;
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.viewportPanelId.empty()) {
|
||||
m_lastStatus = result.viewportPanelId;
|
||||
m_lastMessage = DescribeViewportEvent(result.viewportInputFrame);
|
||||
m_lastColor =
|
||||
result.viewportInputFrame.captureStarted || result.viewportInputFrame.focusGained
|
||||
? kSuccess
|
||||
: kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.requestPointerCapture) {
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "宿主已收到 root shell 返回的 pointer capture 请求。";
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.releasePointerCapture) {
|
||||
m_lastStatus = "Release";
|
||||
m_lastMessage = "宿主已执行 root shell 返回的 pointer release。";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.consumed) {
|
||||
m_lastStatus = "NoOp";
|
||||
m_lastMessage = "这次输入被根壳交互层消费,但没有触发额外状态变化。";
|
||||
@@ -738,27 +868,29 @@ void ScenarioApp::RenderFrame() {
|
||||
m_cachedModel = BuildInteractionModel();
|
||||
m_cachedFrame = UpdateUIEditorShellInteraction(
|
||||
m_interactionState,
|
||||
m_controller,
|
||||
m_shellRect,
|
||||
m_controller.GetPanelRegistry(),
|
||||
m_controller.GetWorkspace(),
|
||||
m_controller.GetSession(),
|
||||
m_cachedModel,
|
||||
m_pendingInputEvents);
|
||||
m_pendingInputEvents.clear();
|
||||
ApplyHostCaptureRequests(m_cachedFrame.result);
|
||||
SetInteractionResult(m_cachedFrame.result);
|
||||
|
||||
if (m_cachedFrame.result.commandTriggered) {
|
||||
m_cachedModel = BuildInteractionModel();
|
||||
m_cachedFrame = UpdateUIEditorShellInteraction(
|
||||
m_interactionState,
|
||||
m_controller,
|
||||
m_shellRect,
|
||||
m_controller.GetPanelRegistry(),
|
||||
m_controller.GetWorkspace(),
|
||||
m_controller.GetSession(),
|
||||
m_cachedModel,
|
||||
{});
|
||||
}
|
||||
|
||||
const auto* viewportFrame =
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.workspaceInteractionFrame.composeFrame, "scene");
|
||||
const std::string selectedPresentation =
|
||||
viewportFrame != nullptr ? "ViewportShell" : "DockHost Placeholder";
|
||||
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
@@ -772,9 +904,9 @@ void ScenarioApp::RenderFrame() {
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一稳定。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单时,child popup 是否直接展开,不需要额外点击。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否能正确收起 popup chain。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证预览区是真实 root shell:MenuBar + Workspace + StatusBar + popup overlay。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证 command 只通过最小 dispatch hook 回传,不接旧 editor 业务。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`,再按 Esc 或点预览区外空白处。", kTextWeak, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时会屏蔽 workspace 输入;菜单关闭后,workspace 交互立即恢复。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证 root shell 会继续透传 viewport / splitter 的 capture 请求,不接旧 editor 业务。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`,点预览外空白处,再点 `Document` 或拖 splitter。", kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只保留这个场景必要的控制。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
@@ -791,13 +923,16 @@ void ScenarioApp::RenderFrame() {
|
||||
addStateLine("Open Root", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kTextPrimary);
|
||||
addStateLine("Popup Chain", JoinPopupChainIds(m_interactionState), kTextPrimary, 11.0f);
|
||||
addStateLine("Submenu Path", JoinSubmenuPathIds(m_interactionState), kTextPrimary, 11.0f);
|
||||
addStateLine("Selected Presentation", selectedPresentation, kTextPrimary, 11.0f);
|
||||
addStateLine("Active Panel", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f);
|
||||
addStateLine("Focused", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
|
||||
addStateLine("Result", m_lastStatus, m_lastColor);
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
|
||||
stateY += 34.0f;
|
||||
addStateLine("Visible Panels", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kTextWeak, 11.0f);
|
||||
addStateLine("Host Capture", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 11.0f);
|
||||
addStateLine(
|
||||
"Capture",
|
||||
"Screenshot",
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
|
||||
@@ -12,9 +12,9 @@ using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
@@ -30,6 +30,7 @@ using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionModel;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionPopupItemRequest;
|
||||
using XCEngine::UI::Editor::UIEditorShellInteractionState;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
|
||||
@@ -149,6 +150,10 @@ UIEditorShellInteractionModel BuildInteractionModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceController BuildController() {
|
||||
return BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
}
|
||||
|
||||
const UIEditorShellInteractionMenuButtonRequest* FindMenuButton(
|
||||
const UIEditorShellInteractionFrame& frame,
|
||||
std::string_view menuId) {
|
||||
@@ -210,28 +215,21 @@ UIInputEvent MakeFocusLost() {
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorShellInteractionTest, ClickMenuBarRootOpensSingleRootPopup) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
const auto request = ResolveUIEditorShellInteractionRequest(
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
controller,
|
||||
model,
|
||||
{},
|
||||
state);
|
||||
ASSERT_EQ(request.menuButtons.size(), 2u);
|
||||
|
||||
const auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(RectCenter(request.menuButtons.front().rect)) });
|
||||
|
||||
@@ -243,18 +241,14 @@ TEST(UIEditorShellInteractionTest, ClickMenuBarRootOpensSingleRootPopup) {
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, HoverOtherRootSwitchesRootPopup) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
const auto* windowButton = FindMenuButton(frame, "window");
|
||||
@@ -262,10 +256,8 @@ TEST(UIEditorShellInteractionTest, HoverOtherRootSwitchesRootPopup) {
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakePointerMove(RectCenter(windowButton->rect)) });
|
||||
|
||||
@@ -276,18 +268,14 @@ TEST(UIEditorShellInteractionTest, HoverOtherRootSwitchesRootPopup) {
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapsesChain) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools");
|
||||
@@ -296,10 +284,8 @@ TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapses
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakePointerMove(RectCenter(submenuItem->rect)) });
|
||||
|
||||
@@ -309,10 +295,8 @@ TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapses
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeKeyDown(KeyCode::Escape) });
|
||||
EXPECT_EQ(frame.openRootMenuId, "file");
|
||||
@@ -320,10 +304,8 @@ TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapses
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeKeyDown(KeyCode::Escape) });
|
||||
EXPECT_FALSE(state.menuSession.HasOpenMenu());
|
||||
@@ -331,18 +313,14 @@ TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapses
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, ClickCommandReturnsDispatchHookAndClosesMenu) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
const auto* commandItem = FindPopupItem(frame, "file-close");
|
||||
@@ -350,10 +328,8 @@ TEST(UIEditorShellInteractionTest, ClickCommandReturnsDispatchHookAndClosesMenu)
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(RectCenter(commandItem->rect)) });
|
||||
|
||||
@@ -364,28 +340,22 @@ TEST(UIEditorShellInteractionTest, ClickCommandReturnsDispatchHookAndClosesMenu)
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
ASSERT_TRUE(state.menuSession.HasOpenMenu());
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(900.0f, 500.0f)) });
|
||||
|
||||
@@ -395,19 +365,15 @@ TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) {
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, DisabledSubmenuDoesNotOpenChildPopup) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
auto model = BuildInteractionModel();
|
||||
model.resolvedMenuModel.menus[0].items[0].enabled = false;
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools");
|
||||
@@ -417,10 +383,8 @@ TEST(UIEditorShellInteractionTest, DisabledSubmenuDoesNotOpenChildPopup) {
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakePointerMove(RectCenter(submenuItem->rect)) });
|
||||
|
||||
@@ -430,18 +394,14 @@ TEST(UIEditorShellInteractionTest, DisabledSubmenuDoesNotOpenChildPopup) {
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, FocusLostDismissesWholeMenuChain) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools");
|
||||
@@ -449,20 +409,16 @@ TEST(UIEditorShellInteractionTest, FocusLostDismissesWholeMenuChain) {
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakePointerMove(RectCenter(submenuItem->rect)) });
|
||||
ASSERT_EQ(frame.request.popupRequests.size(), 2u);
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeFocusLost() });
|
||||
|
||||
@@ -474,18 +430,14 @@ TEST(UIEditorShellInteractionTest, FocusLostDismissesWholeMenuChain) {
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, OpenMenuConsumesWorkspacePointerDownForThatFrame) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
ASSERT_TRUE(state.menuSession.HasOpenMenu());
|
||||
@@ -495,31 +447,100 @@ TEST(UIEditorShellInteractionTest, OpenMenuConsumesWorkspacePointerDownForThatFr
|
||||
frame.shellFrame.workspaceFrame.viewportFrames.front().viewportShellFrame.slotLayout.inputRect;
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(RectCenter(inputRect)) });
|
||||
|
||||
EXPECT_FALSE(frame.result.workspaceResult.consumed);
|
||||
EXPECT_FALSE(frame.shellFrame.workspaceFrame.viewportFrames.front()
|
||||
.viewportShellFrame.inputFrame.pointerPressedInside);
|
||||
EXPECT_FALSE(state.menuSession.HasOpenMenu());
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, InvalidResolvedMenuStateClosesInvisibleModalChain) {
|
||||
const auto registry = BuildPanelRegistry();
|
||||
const auto workspace = BuildWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
TEST(UIEditorShellInteractionTest, MenuClosedBubblesViewportCaptureFromWorkspaceInteraction) {
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{});
|
||||
ASSERT_FALSE(frame.shellFrame.workspaceFrame.viewportFrames.empty());
|
||||
|
||||
const UIRect inputRect =
|
||||
frame.shellFrame.workspaceFrame.viewportFrames.front().viewportShellFrame.slotLayout.inputRect;
|
||||
const UIPoint center = RectCenter(inputRect);
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{
|
||||
MakePointerMove(center),
|
||||
MakeLeftPointerDown(center)
|
||||
});
|
||||
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_TRUE(frame.result.requestPointerCapture);
|
||||
EXPECT_TRUE(frame.result.workspaceResult.requestPointerCapture);
|
||||
EXPECT_EQ(frame.result.viewportPanelId, "scene");
|
||||
EXPECT_TRUE(frame.result.viewportInputFrame.captureStarted);
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, ClosingMenuRestoresWorkspaceInteractionOnNextFrame) {
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
ASSERT_TRUE(state.menuSession.HasOpenMenu());
|
||||
ASSERT_FALSE(frame.shellFrame.workspaceFrame.viewportFrames.empty());
|
||||
|
||||
const UIRect inputRect =
|
||||
frame.shellFrame.workspaceFrame.viewportFrames.front().viewportShellFrame.slotLayout.inputRect;
|
||||
const UIPoint center = RectCenter(inputRect);
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(900.0f, 500.0f)) });
|
||||
EXPECT_FALSE(state.menuSession.HasOpenMenu());
|
||||
EXPECT_FALSE(frame.result.workspaceResult.consumed);
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
model,
|
||||
{
|
||||
MakePointerMove(center),
|
||||
MakeLeftPointerDown(center)
|
||||
});
|
||||
|
||||
EXPECT_TRUE(frame.result.workspaceResult.consumed);
|
||||
EXPECT_TRUE(frame.result.requestPointerCapture);
|
||||
EXPECT_EQ(frame.result.viewportPanelId, "scene");
|
||||
}
|
||||
|
||||
TEST(UIEditorShellInteractionTest, InvalidResolvedMenuStateClosesInvisibleModalChain) {
|
||||
auto controller = BuildController();
|
||||
const auto model = BuildInteractionModel();
|
||||
|
||||
UIEditorShellInteractionState state = {};
|
||||
auto frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
{ MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) });
|
||||
ASSERT_TRUE(state.menuSession.HasOpenMenu());
|
||||
@@ -530,10 +551,8 @@ TEST(UIEditorShellInteractionTest, InvalidResolvedMenuStateClosesInvisibleModalC
|
||||
|
||||
frame = UpdateUIEditorShellInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
updatedModel,
|
||||
{});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user