Unlink XCNewEditor default shell from ImGui compat slice
This commit is contained in:
@@ -135,8 +135,8 @@ Current gap:
|
|||||||
|
|
||||||
Current gap:
|
Current gap:
|
||||||
|
|
||||||
- The default shell host is now native, but the legacy ImGui shell and panel path still exists as a compatibility host and is still compiled into `new_editor`.
|
- The default shell host is now native, and the legacy ImGui shell/panel path has been split out of the default executable into the standalone `XCNewEditorImGuiCompat` compatibility slice.
|
||||||
- The default native shell path is now split away from direct `ImGui::*` calls at the main-target header/include level, but the legacy ImGui shell and panel implementations still remain compiled behind `XCNewEditorImGuiCompat`.
|
- The default native shell path is now split away from direct `ImGui::*` calls at the main-target header/include level and no longer links the compatibility slice by default.
|
||||||
- The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document.
|
- The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document.
|
||||||
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules.
|
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules.
|
||||||
- The default native text path now uses a standalone Windows/GDI atlas through `XCUIStandaloneTextAtlasProvider`, but that provider still lives inside `new_editor` and is not yet promoted into a shared/cross-platform text subsystem.
|
- The default native text path now uses a standalone Windows/GDI atlas through `XCUIStandaloneTextAtlasProvider`, but that provider still lives inside `new_editor` and is not yet promoted into a shared/cross-platform text subsystem.
|
||||||
@@ -309,7 +309,7 @@ Current gap:
|
|||||||
|
|
||||||
- Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage.
|
- Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage.
|
||||||
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
|
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
|
||||||
- `XCNewEditor` no longer depends on ImGui at the default-path header/include level, but `XCNewEditorImGuiCompat` is still linked into the executable and still houses the legacy shell/panel path.
|
- `XCNewEditor` no longer depends on ImGui at the default-path header/include level and no longer links `XCNewEditorImGuiCompat`; the legacy shell/panel path now lives only in the standalone compatibility slice.
|
||||||
- Legacy panel implementations such as `XCUIDemoPanel` / `XCUILayoutLabPanel` still render as ImGui windows inside the compatibility slice, so editor-layer behavior is not yet fully carried by XCUI-native shell composition.
|
- Legacy panel implementations such as `XCUIDemoPanel` / `XCUILayoutLabPanel` still render as ImGui windows inside the compatibility slice, so editor-layer behavior is not yet fully carried by XCUI-native shell composition.
|
||||||
- The default native text path now owns its atlas without ImGui, but the provider is still Windows-only and remains trapped inside `new_editor` instead of a shared/cross-platform text layer.
|
- The default native text path now owns its atlas without ImGui, but the provider is still Windows-only and remains trapped inside `new_editor` instead of a shared/cross-platform text layer.
|
||||||
- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path.
|
- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path.
|
||||||
@@ -318,7 +318,7 @@ Current gap:
|
|||||||
## Execution-Plan Alignment
|
## Execution-Plan Alignment
|
||||||
|
|
||||||
- Against `XCUI完整架构设计与执行计划.md`, current `new_editor` progress should be treated as an early `Phase 8` foothold rather than full `Milestone E` completion:
|
- Against `XCUI完整架构设计与执行计划.md`, current `new_editor` progress should be treated as an early `Phase 8` foothold rather than full `Milestone E` completion:
|
||||||
- landed: `NativeWindowUICompositor`, native shell packet composition, native hosted-preview publication, XCUI-owned texture registrations, native panel surface-image presentation, standalone native text-atlas ownership inside `new_editor`, legacy `Application` TU split, `XCNewEditorImGuiCompat`, main-target header/include isolation from ImGui, and compat-only factory/font seam tightening
|
- landed: `NativeWindowUICompositor`, native shell packet composition, native hosted-preview publication, XCUI-owned texture registrations, native panel surface-image presentation, standalone native text-atlas ownership inside `new_editor`, legacy `Application` TU split, `XCNewEditorImGuiCompat`, main-target header/include isolation from ImGui, default-executable unlinking from the compatibility slice, compat-only factory/font seam tightening, shared native shell layout helpers, and shared panel-chrome/flat-hierarchy helpers
|
||||||
- not yet landed: promotion of the native text-atlas path into a shared/cross-platform text subsystem, shared XCUI-authored editor shell chrome, and retirement of legacy ImGui-window panel implementations
|
- not yet landed: promotion of the native text-atlas path into a shared/cross-platform text subsystem, shared XCUI-authored editor shell chrome, and retirement of legacy ImGui-window panel implementations
|
||||||
- That means the next de-ImGui push should not keep centering on hosted-preview publication; that milestone is now effectively closed for the default native shell path.
|
- That means the next de-ImGui push should not keep centering on hosted-preview publication; that milestone is now effectively closed for the default native shell path.
|
||||||
- The real remaining default-path blockers are:
|
- The real remaining default-path blockers are:
|
||||||
|
|||||||
126
engine/include/XCEngine/UI/Widgets/UIEditorPanelChrome.h
Normal file
126
engine/include/XCEngine/UI/Widgets/UIEditorPanelChrome.h
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/UI/DrawData.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace UI {
|
||||||
|
namespace Widgets {
|
||||||
|
|
||||||
|
struct UIEditorPanelChromeState {
|
||||||
|
bool active = false;
|
||||||
|
bool hovered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIEditorPanelChromeText {
|
||||||
|
std::string_view title = {};
|
||||||
|
std::string_view subtitle = {};
|
||||||
|
std::string_view footer = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIEditorPanelChromeMetrics {
|
||||||
|
float cornerRounding = 18.0f;
|
||||||
|
float headerHeight = 42.0f;
|
||||||
|
float titleInsetX = 16.0f;
|
||||||
|
float titleInsetY = 12.0f;
|
||||||
|
float subtitleInsetY = 28.0f;
|
||||||
|
float footerInsetX = 16.0f;
|
||||||
|
float footerInsetBottom = 18.0f;
|
||||||
|
float activeBorderThickness = 2.0f;
|
||||||
|
float inactiveBorderThickness = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIEditorPanelChromePalette {
|
||||||
|
UIColor surfaceColor = UIColor(9.0f / 255.0f, 13.0f / 255.0f, 18.0f / 255.0f, 212.0f / 255.0f);
|
||||||
|
UIColor borderColor = UIColor(53.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f);
|
||||||
|
UIColor accentColor = UIColor(84.0f / 255.0f, 176.0f / 255.0f, 244.0f / 255.0f, 1.0f);
|
||||||
|
UIColor hoveredAccentColor = UIColor(1.0f, 206.0f / 255.0f, 112.0f / 255.0f, 1.0f);
|
||||||
|
UIColor headerColor = UIColor(13.0f / 255.0f, 20.0f / 255.0f, 28.0f / 255.0f, 242.0f / 255.0f);
|
||||||
|
UIColor textPrimary = UIColor(232.0f / 255.0f, 238.0f / 255.0f, 246.0f / 255.0f, 1.0f);
|
||||||
|
UIColor textSecondary = UIColor(150.0f / 255.0f, 164.0f / 255.0f, 184.0f / 255.0f, 1.0f);
|
||||||
|
UIColor textMuted = UIColor(108.0f / 255.0f, 123.0f / 255.0f, 145.0f / 255.0f, 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline UIRect BuildUIEditorPanelChromeHeaderRect(
|
||||||
|
const UIRect& panelRect,
|
||||||
|
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||||
|
return UIRect(
|
||||||
|
panelRect.x,
|
||||||
|
panelRect.y,
|
||||||
|
panelRect.width,
|
||||||
|
metrics.headerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline UIColor ResolveUIEditorPanelChromeBorderColor(
|
||||||
|
const UIEditorPanelChromeState& state,
|
||||||
|
const UIEditorPanelChromePalette& palette = {}) {
|
||||||
|
if (state.active) {
|
||||||
|
return palette.accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.hovered) {
|
||||||
|
return palette.hoveredAccentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return palette.borderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ResolveUIEditorPanelChromeBorderThickness(
|
||||||
|
const UIEditorPanelChromeState& state,
|
||||||
|
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||||
|
return state.active
|
||||||
|
? metrics.activeBorderThickness
|
||||||
|
: metrics.inactiveBorderThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AppendUIEditorPanelChromeBackground(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& panelRect,
|
||||||
|
const UIEditorPanelChromeState& state,
|
||||||
|
const UIEditorPanelChromePalette& palette = {},
|
||||||
|
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||||
|
drawList.AddFilledRect(panelRect, palette.surfaceColor, metrics.cornerRounding);
|
||||||
|
drawList.AddRectOutline(
|
||||||
|
panelRect,
|
||||||
|
ResolveUIEditorPanelChromeBorderColor(state, palette),
|
||||||
|
ResolveUIEditorPanelChromeBorderThickness(state, metrics),
|
||||||
|
metrics.cornerRounding);
|
||||||
|
drawList.AddFilledRect(
|
||||||
|
BuildUIEditorPanelChromeHeaderRect(panelRect, metrics),
|
||||||
|
palette.headerColor,
|
||||||
|
metrics.cornerRounding);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AppendUIEditorPanelChromeForeground(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& panelRect,
|
||||||
|
const UIEditorPanelChromeText& text,
|
||||||
|
const UIEditorPanelChromePalette& palette = {},
|
||||||
|
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||||
|
if (!text.title.empty()) {
|
||||||
|
drawList.AddText(
|
||||||
|
UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.titleInsetY),
|
||||||
|
std::string(text.title),
|
||||||
|
palette.textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text.subtitle.empty()) {
|
||||||
|
drawList.AddText(
|
||||||
|
UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.subtitleInsetY),
|
||||||
|
std::string(text.subtitle),
|
||||||
|
palette.textSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text.footer.empty()) {
|
||||||
|
drawList.AddText(
|
||||||
|
UIPoint(panelRect.x + metrics.footerInsetX, panelRect.y + panelRect.height - metrics.footerInsetBottom),
|
||||||
|
std::string(text.footer),
|
||||||
|
palette.textMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Widgets
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace XCEngine
|
||||||
167
engine/include/XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h
Normal file
167
engine/include/XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <span>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace UI {
|
||||||
|
namespace Widgets {
|
||||||
|
|
||||||
|
inline constexpr std::size_t kInvalidUIFlatHierarchyItemOffset = static_cast<std::size_t>(-1);
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
template <typename ResolveDepthFn>
|
||||||
|
float ResolveUIFlatHierarchyDepth(
|
||||||
|
std::span<const std::size_t> itemIndices,
|
||||||
|
std::size_t itemOffset,
|
||||||
|
ResolveDepthFn&& resolveDepth) {
|
||||||
|
if (itemOffset >= itemIndices.size()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float depth = static_cast<float>(resolveDepth(itemIndices[itemOffset]));
|
||||||
|
return depth > 0.0f ? depth : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
template <typename ResolveDepthFn>
|
||||||
|
bool UIFlatHierarchyHasChildren(
|
||||||
|
std::span<const std::size_t> itemIndices,
|
||||||
|
std::size_t itemOffset,
|
||||||
|
ResolveDepthFn&& resolveDepth) {
|
||||||
|
if (itemOffset >= itemIndices.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float itemDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
itemOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
for (std::size_t candidateOffset = itemOffset + 1u;
|
||||||
|
candidateOffset < itemIndices.size();
|
||||||
|
++candidateOffset) {
|
||||||
|
const float candidateDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
candidateOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (candidateDepth <= itemDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ResolveDepthFn>
|
||||||
|
std::size_t UIFlatHierarchyFindParentOffset(
|
||||||
|
std::span<const std::size_t> itemIndices,
|
||||||
|
std::size_t itemOffset,
|
||||||
|
ResolveDepthFn&& resolveDepth) {
|
||||||
|
if (itemOffset >= itemIndices.size()) {
|
||||||
|
return kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float itemDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
itemOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (itemDepth <= 0.0f) {
|
||||||
|
return kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t candidateOffset = itemOffset; candidateOffset > 0u; --candidateOffset) {
|
||||||
|
const std::size_t previousOffset = candidateOffset - 1u;
|
||||||
|
const float previousDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
previousOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (previousDepth < itemDepth) {
|
||||||
|
return previousOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ResolveDepthFn, typename IsExpandedFn>
|
||||||
|
bool UIFlatHierarchyIsVisible(
|
||||||
|
std::span<const std::size_t> itemIndices,
|
||||||
|
std::size_t itemOffset,
|
||||||
|
ResolveDepthFn&& resolveDepth,
|
||||||
|
IsExpandedFn&& isExpanded) {
|
||||||
|
if (itemOffset >= itemIndices.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float requiredAncestorDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
itemOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (requiredAncestorDepth <= 0.0f) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t candidateOffset = itemOffset;
|
||||||
|
candidateOffset > 0u && requiredAncestorDepth > 0.0f;
|
||||||
|
--candidateOffset) {
|
||||||
|
const std::size_t previousOffset = candidateOffset - 1u;
|
||||||
|
const float previousDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
previousOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (previousDepth < requiredAncestorDepth) {
|
||||||
|
if (!isExpanded(itemIndices[previousOffset])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredAncestorDepth = previousDepth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ResolveDepthFn, typename IsVisibleFn>
|
||||||
|
std::size_t UIFlatHierarchyFindFirstVisibleChildOffset(
|
||||||
|
std::span<const std::size_t> itemIndices,
|
||||||
|
std::size_t itemOffset,
|
||||||
|
ResolveDepthFn&& resolveDepth,
|
||||||
|
IsVisibleFn&& isVisible) {
|
||||||
|
if (!UIFlatHierarchyHasChildren(
|
||||||
|
itemIndices,
|
||||||
|
itemOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth))) {
|
||||||
|
return kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float itemDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
itemOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
for (std::size_t candidateOffset = itemOffset + 1u;
|
||||||
|
candidateOffset < itemIndices.size();
|
||||||
|
++candidateOffset) {
|
||||||
|
const float candidateDepth = Detail::ResolveUIFlatHierarchyDepth(
|
||||||
|
itemIndices,
|
||||||
|
candidateOffset,
|
||||||
|
std::forward<ResolveDepthFn>(resolveDepth));
|
||||||
|
if (candidateDepth <= itemDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVisible(itemIndices[candidateOffset])) {
|
||||||
|
return candidateOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Widgets
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -44,6 +44,7 @@ endif()
|
|||||||
set(NEW_EDITOR_SOURCES
|
set(NEW_EDITOR_SOURCES
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/Application.cpp
|
src/Application.cpp
|
||||||
|
src/ApplicationDefaultShell.cpp
|
||||||
src/panels/Panel.cpp
|
src/panels/Panel.cpp
|
||||||
src/Rendering/MainWindowBackdropPass.cpp
|
src/Rendering/MainWindowBackdropPass.cpp
|
||||||
src/Rendering/MainWindowNativeBackdropRenderer.cpp
|
src/Rendering/MainWindowNativeBackdropRenderer.cpp
|
||||||
@@ -156,7 +157,6 @@ target_link_libraries(XCNewEditorImGuiCompat PRIVATE
|
|||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
XCEngine
|
XCEngine
|
||||||
XCNewEditorImGuiCompat
|
|
||||||
d3d12.lib
|
d3d12.lib
|
||||||
dxgi.lib
|
dxgi.lib
|
||||||
user32
|
user32
|
||||||
|
|||||||
138
new_editor/src/ApplicationDefaultShell.cpp
Normal file
138
new_editor/src/ApplicationDefaultShell.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||||
|
#include "panels/XCUIDemoPanel.h"
|
||||||
|
#include "panels/XCUILayoutLabPanel.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace NewEditor {
|
||||||
|
|
||||||
|
Application::Application() = default;
|
||||||
|
Application::~Application() = default;
|
||||||
|
|
||||||
|
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
||||||
|
Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
||||||
|
if (nativePreview) {
|
||||||
|
return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter(
|
||||||
|
m_hostedPreviewQueue,
|
||||||
|
m_hostedPreviewSurfaceRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ::XCEngine::Editor::XCUIBackend::CreateNullXCUIHostedPreviewPresenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::InitializePanelsForActiveWindowHost() {
|
||||||
|
InitializeNativeShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::InitializeLegacyImGuiPanels() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::ConfigureHostedPreviewPresenters() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::ResetLegacyPanels() {
|
||||||
|
m_demoPanel.reset();
|
||||||
|
m_layoutLabPanel.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::SyncShellChromePanelStateFromPanels() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::ConfigureShellCommandRouter() {
|
||||||
|
m_shellCommandRouter.Clear();
|
||||||
|
|
||||||
|
ShellCommandBindings bindings = {};
|
||||||
|
bindings.getXCUIDemoPanelVisible = [this]() {
|
||||||
|
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
|
||||||
|
};
|
||||||
|
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
|
||||||
|
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible);
|
||||||
|
};
|
||||||
|
bindings.getXCUILayoutLabPanelVisible = [this]() {
|
||||||
|
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
|
||||||
|
};
|
||||||
|
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
|
||||||
|
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible);
|
||||||
|
};
|
||||||
|
bindings.getNativeBackdropVisible = [this]() {
|
||||||
|
return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||||
|
};
|
||||||
|
bindings.setNativeBackdropVisible = [this](bool visible) {
|
||||||
|
SetShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop, visible);
|
||||||
|
};
|
||||||
|
bindings.getPulseAccentEnabled = [this]() {
|
||||||
|
return IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||||
|
};
|
||||||
|
bindings.setPulseAccentEnabled = [this](bool enabled) {
|
||||||
|
SetShellViewToggleEnabled(ShellViewToggleId::PulseAccent, enabled);
|
||||||
|
};
|
||||||
|
bindings.getNativeXCUIOverlayVisible = [this]() {
|
||||||
|
return IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay);
|
||||||
|
};
|
||||||
|
bindings.setNativeXCUIOverlayVisible = [this](bool visible) {
|
||||||
|
SetShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay, visible);
|
||||||
|
};
|
||||||
|
bindings.getHostedPreviewHudVisible = [this]() {
|
||||||
|
return IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
|
||||||
|
};
|
||||||
|
bindings.setHostedPreviewHudVisible = [this](bool visible) {
|
||||||
|
SetShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud, visible);
|
||||||
|
};
|
||||||
|
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
|
||||||
|
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
|
||||||
|
};
|
||||||
|
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
|
||||||
|
m_shellChromeState.SetHostedPreviewMode(
|
||||||
|
ShellPanelId::XCUIDemo,
|
||||||
|
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||||
|
};
|
||||||
|
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
|
||||||
|
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
||||||
|
};
|
||||||
|
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
|
||||||
|
m_shellChromeState.SetHostedPreviewMode(
|
||||||
|
ShellPanelId::XCUILayoutLab,
|
||||||
|
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
||||||
|
Application::DispatchShellShortcuts(
|
||||||
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||||
|
if (!m_shellInputBridge.HasBaseline()) {
|
||||||
|
m_shellInputBridge.Prime(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
||||||
|
m_shellInputBridge.Translate(snapshot);
|
||||||
|
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
||||||
|
Application::BuildShellShortcutSnapshot(frameDelta);
|
||||||
|
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
||||||
|
return frameDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::InitializeWindowCompositor() {
|
||||||
|
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor();
|
||||||
|
if (m_windowCompositor != nullptr) {
|
||||||
|
m_windowCompositor->Initialize(m_hwnd, m_windowRenderer, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::FrameLegacyImGuiHost() {
|
||||||
|
m_xcuiInputSource.ClearFrameTransients();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::RenderLegacyImGuiUiFrame() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::RenderShellChrome() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::RenderHostedPreviewHud() {
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace NewEditor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <XCEngine/UI/Types.h>
|
#include <XCEngine/UI/Types.h>
|
||||||
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
|
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
|
||||||
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
||||||
|
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
|
||||||
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
|
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
|
||||||
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
||||||
|
|
||||||
@@ -556,23 +557,17 @@ bool HasTreeItemChildren(const RuntimeBuildContext& state, std::size_t nodeIndex
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float indentLevel = ResolveTreeIndentLevel(node);
|
|
||||||
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
||||||
if (siblingIt == parent.children.end()) {
|
if (siblingIt == parent.children.end()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = siblingIt + 1; it != parent.children.end(); ++it) {
|
return UIWidgets::UIFlatHierarchyHasChildren(
|
||||||
const LayoutNode& sibling = state.nodes[*it];
|
std::span<const std::size_t>(parent.children),
|
||||||
const float siblingIndentLevel = ResolveTreeIndentLevel(sibling);
|
static_cast<std::size_t>(siblingIt - parent.children.begin()),
|
||||||
if (siblingIndentLevel <= indentLevel) {
|
[&state](std::size_t siblingIndex) {
|
||||||
break;
|
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
|
||||||
}
|
});
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsNodeCollapsible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
bool IsNodeCollapsible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
||||||
@@ -626,32 +621,20 @@ bool IsNodeVisible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float requiredAncestorIndent = ResolveTreeIndentLevel(node);
|
|
||||||
if (requiredAncestorIndent <= 0.0f) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
|
||||||
if (siblingIt == parent.children.end()) {
|
if (siblingIt == parent.children.end()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t siblingOffset =
|
return UIWidgets::UIFlatHierarchyIsVisible(
|
||||||
static_cast<std::size_t>(siblingIt - parent.children.begin());
|
std::span<const std::size_t>(parent.children),
|
||||||
for (std::size_t offset = siblingOffset; offset > 0u && requiredAncestorIndent > 0.0f; --offset) {
|
static_cast<std::size_t>(siblingIt - parent.children.begin()),
|
||||||
const std::size_t siblingIndex = parent.children[offset - 1u];
|
[&state](std::size_t siblingIndex) {
|
||||||
const LayoutNode& sibling = state.nodes[siblingIndex];
|
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
|
||||||
const float siblingIndent = ResolveTreeIndentLevel(sibling);
|
},
|
||||||
if (siblingIndent < requiredAncestorIndent) {
|
[&state](std::size_t siblingIndex) {
|
||||||
if (!IsNodeExpanded(state, siblingIndex)) {
|
return IsNodeExpanded(state, siblingIndex);
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
requiredAncestorIndent = siblingIndent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::size_t> CollectVisibleChildren(
|
std::vector<std::size_t> CollectVisibleChildren(
|
||||||
@@ -961,14 +944,17 @@ std::size_t FindTreeParentItemIndex(
|
|||||||
return kInvalidIndex;
|
return kInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = siblingIt; it != treeView.children.begin();) {
|
const std::size_t parentOffset = UIWidgets::UIFlatHierarchyFindParentOffset(
|
||||||
--it;
|
std::span<const std::size_t>(treeView.children),
|
||||||
if (ResolveTreeIndentLevel(state.nodes[*it]) < indentLevel) {
|
static_cast<std::size_t>(siblingIt - treeView.children.begin()),
|
||||||
return *it;
|
[&state](std::size_t siblingIndex) {
|
||||||
}
|
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
|
||||||
|
});
|
||||||
|
if (parentOffset == UIWidgets::kInvalidUIFlatHierarchyItemOffset) {
|
||||||
|
return kInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return kInvalidIndex;
|
return treeView.children[parentOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t FindFirstTreeChildItemIndex(
|
std::size_t FindFirstTreeChildItemIndex(
|
||||||
@@ -981,27 +967,27 @@ std::size_t FindFirstTreeChildItemIndex(
|
|||||||
|
|
||||||
const LayoutNode& node = state.nodes[nodeIndex];
|
const LayoutNode& node = state.nodes[nodeIndex];
|
||||||
const LayoutNode& treeView = state.nodes[node.parentIndex];
|
const LayoutNode& treeView = state.nodes[node.parentIndex];
|
||||||
const float indentLevel = ResolveTreeIndentLevel(node);
|
|
||||||
const auto siblingIt = std::find(treeView.children.begin(), treeView.children.end(), nodeIndex);
|
const auto siblingIt = std::find(treeView.children.begin(), treeView.children.end(), nodeIndex);
|
||||||
if (siblingIt == treeView.children.end()) {
|
if (siblingIt == treeView.children.end()) {
|
||||||
return kInvalidIndex;
|
return kInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = siblingIt + 1; it != treeView.children.end(); ++it) {
|
const std::size_t childOffset = UIWidgets::UIFlatHierarchyFindFirstVisibleChildOffset(
|
||||||
const std::size_t candidateIndex = *it;
|
std::span<const std::size_t>(treeView.children),
|
||||||
const float candidateIndent = ResolveTreeIndentLevel(state.nodes[candidateIndex]);
|
static_cast<std::size_t>(siblingIt - treeView.children.begin()),
|
||||||
if (candidateIndent <= indentLevel) {
|
[&state](std::size_t siblingIndex) {
|
||||||
break;
|
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
|
||||||
}
|
},
|
||||||
|
[&state](std::size_t siblingIndex) {
|
||||||
if (IsNodeVisible(state, candidateIndex)) {
|
return IsNodeVisible(state, siblingIndex);
|
||||||
return candidateIndex;
|
});
|
||||||
}
|
if (childOffset == UIWidgets::kInvalidUIFlatHierarchyItemOffset) {
|
||||||
}
|
|
||||||
|
|
||||||
return kInvalidIndex;
|
return kInvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return treeView.children[childOffset];
|
||||||
|
}
|
||||||
|
|
||||||
bool HandleKeyboardExpand(RuntimeBuildContext& state) {
|
bool HandleKeyboardExpand(RuntimeBuildContext& state) {
|
||||||
const std::size_t selectedIndex = FindNodeIndexById(
|
const std::size_t selectedIndex = FindNodeIndexById(
|
||||||
state,
|
state,
|
||||||
|
|||||||
227
new_editor/src/XCUIBackend/XCUINativeShellLayout.h
Normal file
227
new_editor/src/XCUIBackend/XCUINativeShellLayout.h
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "XCUIShellChromeState.h"
|
||||||
|
|
||||||
|
#include <XCEngine/UI/Types.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace XCUIBackend {
|
||||||
|
|
||||||
|
struct XCUINativeShellMetrics {
|
||||||
|
float outerMargin = 22.0f;
|
||||||
|
float innerGap = 18.0f;
|
||||||
|
float headerHeight = 58.0f;
|
||||||
|
float footerHeight = 34.0f;
|
||||||
|
float panelHeaderHeight = 42.0f;
|
||||||
|
float panelPadding = 14.0f;
|
||||||
|
float panelMinWidth = 260.0f;
|
||||||
|
float panelMinHeight = 180.0f;
|
||||||
|
float primaryPanelWidthRatio = 0.60f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XCUINativeShellPanelSpec {
|
||||||
|
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
|
||||||
|
std::string_view title = {};
|
||||||
|
bool visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XCUINativeShellPanelLayout {
|
||||||
|
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
|
||||||
|
std::string title = {};
|
||||||
|
UI::UIRect panelRect = {};
|
||||||
|
UI::UIRect canvasRect = {};
|
||||||
|
bool visible = false;
|
||||||
|
bool hovered = false;
|
||||||
|
bool active = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XCUINativeShellLayoutResult {
|
||||||
|
UI::UIRect shellRect = {};
|
||||||
|
UI::UIRect topBarRect = {};
|
||||||
|
UI::UIRect footerRect = {};
|
||||||
|
UI::UIRect workspaceRect = {};
|
||||||
|
std::vector<XCUINativeShellPanelLayout> panelLayouts = {};
|
||||||
|
XCUIShellPanelId activePanel = XCUIShellPanelId::XCUIDemo;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool XCUINativeShellContainsPoint(
|
||||||
|
const UI::UIRect& rect,
|
||||||
|
const UI::UIPoint& point) {
|
||||||
|
return point.x >= rect.x &&
|
||||||
|
point.y >= rect.y &&
|
||||||
|
point.x <= rect.x + rect.width &&
|
||||||
|
point.y <= rect.y + rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline UI::UIRect BuildXCUINativeShellCanvasRect(
|
||||||
|
const UI::UIRect& panelRect,
|
||||||
|
const XCUINativeShellMetrics& metrics = {}) {
|
||||||
|
return UI::UIRect(
|
||||||
|
panelRect.x + metrics.panelPadding,
|
||||||
|
panelRect.y + metrics.panelHeaderHeight,
|
||||||
|
(std::max)(0.0f, panelRect.width - metrics.panelPadding * 2.0f),
|
||||||
|
(std::max)(0.0f, panelRect.height - metrics.panelHeaderHeight - metrics.panelPadding));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline XCUINativeShellPanelLayout BuildXCUINativeShellPanelLayout(
|
||||||
|
const XCUINativeShellPanelSpec& spec,
|
||||||
|
const UI::UIRect& panelRect,
|
||||||
|
const UI::UIPoint& pointerPosition,
|
||||||
|
bool windowFocused,
|
||||||
|
bool active,
|
||||||
|
const XCUINativeShellMetrics& metrics = {}) {
|
||||||
|
XCUINativeShellPanelLayout layout = {};
|
||||||
|
layout.panelId = spec.panelId;
|
||||||
|
layout.title = std::string(spec.title);
|
||||||
|
layout.panelRect = panelRect;
|
||||||
|
layout.canvasRect = BuildXCUINativeShellCanvasRect(panelRect, metrics);
|
||||||
|
layout.visible =
|
||||||
|
spec.visible &&
|
||||||
|
panelRect.width >= metrics.panelMinWidth &&
|
||||||
|
panelRect.height >= metrics.panelMinHeight;
|
||||||
|
layout.hovered = windowFocused && XCUINativeShellContainsPoint(layout.canvasRect, pointerPosition);
|
||||||
|
layout.active = active;
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline XCUIShellPanelId ResolveXCUINativeShellActivePanel(
|
||||||
|
XCUIShellPanelId currentActivePanel,
|
||||||
|
const std::vector<XCUINativeShellPanelLayout>& panelLayouts,
|
||||||
|
bool pointerPressed) {
|
||||||
|
if (panelLayouts.empty()) {
|
||||||
|
return currentActivePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool activePanelStillPresent = false;
|
||||||
|
for (const XCUINativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||||
|
if (panelLayout.panelId == currentActivePanel) {
|
||||||
|
activePanelStillPresent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XCUIShellPanelId resolvedActivePanel = activePanelStillPresent
|
||||||
|
? currentActivePanel
|
||||||
|
: panelLayouts.front().panelId;
|
||||||
|
if (!pointerPressed) {
|
||||||
|
return resolvedActivePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const XCUINativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||||
|
if (panelLayout.hovered) {
|
||||||
|
return panelLayout.panelId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedActivePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline XCUINativeShellLayoutResult BuildXCUINativeShellLayout(
|
||||||
|
const UI::UIRect& shellRect,
|
||||||
|
std::initializer_list<XCUINativeShellPanelSpec> panelSpecs,
|
||||||
|
XCUIShellPanelId currentActivePanel,
|
||||||
|
const UI::UIPoint& pointerPosition,
|
||||||
|
bool windowFocused,
|
||||||
|
bool pointerPressed,
|
||||||
|
const XCUINativeShellMetrics& metrics = {}) {
|
||||||
|
XCUINativeShellLayoutResult result = {};
|
||||||
|
result.shellRect = shellRect;
|
||||||
|
result.activePanel = currentActivePanel;
|
||||||
|
|
||||||
|
result.topBarRect = UI::UIRect(
|
||||||
|
shellRect.x + metrics.outerMargin,
|
||||||
|
shellRect.y + metrics.outerMargin,
|
||||||
|
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||||
|
metrics.headerHeight);
|
||||||
|
|
||||||
|
result.footerRect = UI::UIRect(
|
||||||
|
shellRect.x + metrics.outerMargin,
|
||||||
|
(std::max)(
|
||||||
|
result.topBarRect.y + result.topBarRect.height + metrics.innerGap,
|
||||||
|
shellRect.y + shellRect.height - metrics.outerMargin - metrics.footerHeight),
|
||||||
|
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||||
|
metrics.footerHeight);
|
||||||
|
|
||||||
|
const float workspaceTop = result.topBarRect.y + result.topBarRect.height + metrics.innerGap;
|
||||||
|
const float workspaceBottom = result.footerRect.y - metrics.innerGap;
|
||||||
|
result.workspaceRect = UI::UIRect(
|
||||||
|
shellRect.x + metrics.outerMargin,
|
||||||
|
workspaceTop,
|
||||||
|
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||||
|
(std::max)(0.0f, workspaceBottom - workspaceTop));
|
||||||
|
|
||||||
|
std::vector<XCUINativeShellPanelSpec> visiblePanels = {};
|
||||||
|
visiblePanels.reserve(panelSpecs.size());
|
||||||
|
for (const XCUINativeShellPanelSpec& panelSpec : panelSpecs) {
|
||||||
|
if (panelSpec.visible) {
|
||||||
|
visiblePanels.push_back(panelSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visiblePanels.empty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.panelLayouts.reserve((std::min)(std::size_t(2), visiblePanels.size()));
|
||||||
|
if (visiblePanels.size() >= 2u) {
|
||||||
|
const float leftWidth = (std::max)(
|
||||||
|
metrics.panelMinWidth,
|
||||||
|
(std::min)(
|
||||||
|
result.workspaceRect.width * metrics.primaryPanelWidthRatio,
|
||||||
|
result.workspaceRect.width - metrics.panelMinWidth - metrics.innerGap));
|
||||||
|
const float rightWidth = (std::max)(0.0f, result.workspaceRect.width - leftWidth - metrics.innerGap);
|
||||||
|
|
||||||
|
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||||
|
visiblePanels[0],
|
||||||
|
UI::UIRect(
|
||||||
|
result.workspaceRect.x,
|
||||||
|
result.workspaceRect.y,
|
||||||
|
leftWidth,
|
||||||
|
result.workspaceRect.height),
|
||||||
|
pointerPosition,
|
||||||
|
windowFocused,
|
||||||
|
false,
|
||||||
|
metrics));
|
||||||
|
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||||
|
visiblePanels[1],
|
||||||
|
UI::UIRect(
|
||||||
|
result.workspaceRect.x + leftWidth + metrics.innerGap,
|
||||||
|
result.workspaceRect.y,
|
||||||
|
rightWidth,
|
||||||
|
result.workspaceRect.height),
|
||||||
|
pointerPosition,
|
||||||
|
windowFocused,
|
||||||
|
false,
|
||||||
|
metrics));
|
||||||
|
} else {
|
||||||
|
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||||
|
visiblePanels.front(),
|
||||||
|
result.workspaceRect,
|
||||||
|
pointerPosition,
|
||||||
|
windowFocused,
|
||||||
|
false,
|
||||||
|
metrics));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.activePanel = ResolveXCUINativeShellActivePanel(
|
||||||
|
currentActivePanel,
|
||||||
|
result.panelLayouts,
|
||||||
|
pointerPressed);
|
||||||
|
for (XCUINativeShellPanelLayout& panelLayout : result.panelLayouts) {
|
||||||
|
panelLayout.active = panelLayout.panelId == result.activePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCUIBackend
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -14,13 +15,15 @@ namespace XCUIBackend {
|
|||||||
|
|
||||||
enum class XCUIPanelCanvasHostBackend : std::uint8_t {
|
enum class XCUIPanelCanvasHostBackend : std::uint8_t {
|
||||||
Null = 0,
|
Null = 0,
|
||||||
ImGui
|
ImGui,
|
||||||
|
Native
|
||||||
};
|
};
|
||||||
|
|
||||||
struct XCUIPanelCanvasHostCapabilities {
|
struct XCUIPanelCanvasHostCapabilities {
|
||||||
bool supportsPointerHitTesting = false;
|
bool supportsPointerHitTesting = false;
|
||||||
bool supportsHostedSurfaceImages = false;
|
bool supportsHostedSurfaceImages = false;
|
||||||
bool supportsPrimitiveOverlays = false;
|
bool supportsPrimitiveOverlays = false;
|
||||||
|
bool supportsExternallyDrivenSession = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct XCUIPanelCanvasRequest {
|
struct XCUIPanelCanvasRequest {
|
||||||
@@ -46,6 +49,20 @@ struct XCUIPanelCanvasSession {
|
|||||||
bool windowFocused = false;
|
bool windowFocused = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct XCUIPanelCanvasFrameSnapshot {
|
||||||
|
std::string childId = {};
|
||||||
|
XCUIPanelCanvasSession session = {};
|
||||||
|
bool bordered = true;
|
||||||
|
bool showingSurfaceImage = false;
|
||||||
|
bool drawPreviewFrame = true;
|
||||||
|
std::string placeholderTitle = {};
|
||||||
|
std::string placeholderSubtitle = {};
|
||||||
|
std::string badgeTitle = {};
|
||||||
|
std::string badgeSubtitle = {};
|
||||||
|
XCUIHostedPreviewSurfaceImage surfaceImage = {};
|
||||||
|
::XCEngine::UI::UIDrawData overlayDrawData = {};
|
||||||
|
};
|
||||||
|
|
||||||
inline const char* ResolveXCUIPanelCanvasChildId(
|
inline const char* ResolveXCUIPanelCanvasChildId(
|
||||||
const XCUIPanelCanvasRequest& request,
|
const XCUIPanelCanvasRequest& request,
|
||||||
const char* fallback = "XCUIPanelCanvasHost") {
|
const char* fallback = "XCUIPanelCanvasHost") {
|
||||||
@@ -99,6 +116,10 @@ public:
|
|||||||
const ::XCEngine::UI::UIColor& color,
|
const ::XCEngine::UI::UIColor& color,
|
||||||
float fontSize = 0.0f) = 0;
|
float fontSize = 0.0f) = 0;
|
||||||
virtual void EndCanvas() = 0;
|
virtual void EndCanvas() = 0;
|
||||||
|
virtual bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const {
|
||||||
|
outSnapshot = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost();
|
std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost();
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
set(UI_TEST_SOURCES
|
set(UI_TEST_SOURCES
|
||||||
test_ui_core.cpp
|
test_ui_core.cpp
|
||||||
test_ui_editor_collection_primitives.cpp
|
test_ui_editor_collection_primitives.cpp
|
||||||
|
test_ui_editor_panel_chrome.cpp
|
||||||
test_ui_expansion_model.cpp
|
test_ui_expansion_model.cpp
|
||||||
|
test_ui_flat_hierarchy_helpers.cpp
|
||||||
test_ui_keyboard_navigation_model.cpp
|
test_ui_keyboard_navigation_model.cpp
|
||||||
test_ui_property_edit_model.cpp
|
test_ui_property_edit_model.cpp
|
||||||
test_layout_engine.cpp
|
test_layout_engine.cpp
|
||||||
|
|||||||
126
tests/Core/UI/test_ui_editor_panel_chrome.cpp
Normal file
126
tests/Core/UI/test_ui_editor_panel_chrome.cpp
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/UI/DrawData.h>
|
||||||
|
#include <XCEngine/UI/Widgets/UIEditorPanelChrome.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using XCEngine::UI::UIColor;
|
||||||
|
using XCEngine::UI::UIDrawCommandType;
|
||||||
|
using XCEngine::UI::UIDrawList;
|
||||||
|
using XCEngine::UI::UIRect;
|
||||||
|
using XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground;
|
||||||
|
using XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground;
|
||||||
|
using XCEngine::UI::Widgets::BuildUIEditorPanelChromeHeaderRect;
|
||||||
|
using XCEngine::UI::Widgets::ResolveUIEditorPanelChromeBorderColor;
|
||||||
|
using XCEngine::UI::Widgets::ResolveUIEditorPanelChromeBorderThickness;
|
||||||
|
using XCEngine::UI::Widgets::UIEditorPanelChromePalette;
|
||||||
|
using XCEngine::UI::Widgets::UIEditorPanelChromeState;
|
||||||
|
using XCEngine::UI::Widgets::UIEditorPanelChromeText;
|
||||||
|
|
||||||
|
void ExpectColorEq(
|
||||||
|
const UIColor& actual,
|
||||||
|
const UIColor& expected) {
|
||||||
|
EXPECT_FLOAT_EQ(actual.r, expected.r);
|
||||||
|
EXPECT_FLOAT_EQ(actual.g, expected.g);
|
||||||
|
EXPECT_FLOAT_EQ(actual.b, expected.b);
|
||||||
|
EXPECT_FLOAT_EQ(actual.a, expected.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIEditorPanelChromeTest, HeaderRectAndBorderPolicyMatchNativeShellCardChrome) {
|
||||||
|
const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f);
|
||||||
|
const UIEditorPanelChromePalette palette = {};
|
||||||
|
|
||||||
|
const auto headerRect = BuildUIEditorPanelChromeHeaderRect(panelRect);
|
||||||
|
EXPECT_FLOAT_EQ(headerRect.x, 100.0f);
|
||||||
|
EXPECT_FLOAT_EQ(headerRect.y, 200.0f);
|
||||||
|
EXPECT_FLOAT_EQ(headerRect.width, 320.0f);
|
||||||
|
EXPECT_FLOAT_EQ(headerRect.height, 42.0f);
|
||||||
|
|
||||||
|
ExpectColorEq(
|
||||||
|
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState(), palette),
|
||||||
|
palette.borderColor);
|
||||||
|
ExpectColorEq(
|
||||||
|
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ false, true }, palette),
|
||||||
|
palette.hoveredAccentColor);
|
||||||
|
ExpectColorEq(
|
||||||
|
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ true, true }, palette),
|
||||||
|
palette.accentColor);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState()), 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState{ true, false }), 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIEditorPanelChromeTest, BackgroundAppendEmitsSurfaceOutlineAndHeaderFill) {
|
||||||
|
UIDrawList drawList("PanelChrome");
|
||||||
|
const UIRect panelRect(40.0f, 60.0f, 400.0f, 280.0f);
|
||||||
|
const UIEditorPanelChromeState state{ true, false };
|
||||||
|
const UIEditorPanelChromePalette palette = {};
|
||||||
|
|
||||||
|
AppendUIEditorPanelChromeBackground(drawList, panelRect, state, palette);
|
||||||
|
|
||||||
|
ASSERT_EQ(drawList.GetCommandCount(), 3u);
|
||||||
|
const auto& commands = drawList.GetCommands();
|
||||||
|
EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect);
|
||||||
|
EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline);
|
||||||
|
EXPECT_EQ(commands[2].type, UIDrawCommandType::FilledRect);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(commands[0].rect.x, 40.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[0].rounding, 18.0f);
|
||||||
|
ExpectColorEq(commands[0].color, palette.surfaceColor);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(commands[1].thickness, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[1].rounding, 18.0f);
|
||||||
|
ExpectColorEq(commands[1].color, palette.accentColor);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(commands[2].rect.height, 42.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[2].rounding, 18.0f);
|
||||||
|
ExpectColorEq(commands[2].color, palette.headerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIEditorPanelChromeTest, ForegroundAppendPlacesTitleSubtitleAndFooterAtCurrentOffsets) {
|
||||||
|
UIDrawList drawList("PanelChromeText");
|
||||||
|
const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f);
|
||||||
|
const UIEditorPanelChromePalette palette = {};
|
||||||
|
const UIEditorPanelChromeText text{
|
||||||
|
"XCUI Demo",
|
||||||
|
"native queued offscreen surface",
|
||||||
|
"Active | 42 elements | 9 cmds"
|
||||||
|
};
|
||||||
|
|
||||||
|
AppendUIEditorPanelChromeForeground(drawList, panelRect, text, palette);
|
||||||
|
|
||||||
|
ASSERT_EQ(drawList.GetCommandCount(), 3u);
|
||||||
|
const auto& commands = drawList.GetCommands();
|
||||||
|
EXPECT_EQ(commands[0].type, UIDrawCommandType::Text);
|
||||||
|
EXPECT_EQ(commands[1].type, UIDrawCommandType::Text);
|
||||||
|
EXPECT_EQ(commands[2].type, UIDrawCommandType::Text);
|
||||||
|
|
||||||
|
EXPECT_EQ(commands[0].text, "XCUI Demo");
|
||||||
|
EXPECT_FLOAT_EQ(commands[0].position.x, 116.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[0].position.y, 212.0f);
|
||||||
|
ExpectColorEq(commands[0].color, palette.textPrimary);
|
||||||
|
|
||||||
|
EXPECT_EQ(commands[1].text, "native queued offscreen surface");
|
||||||
|
EXPECT_FLOAT_EQ(commands[1].position.x, 116.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[1].position.y, 228.0f);
|
||||||
|
ExpectColorEq(commands[1].color, palette.textSecondary);
|
||||||
|
|
||||||
|
EXPECT_EQ(commands[2].text, "Active | 42 elements | 9 cmds");
|
||||||
|
EXPECT_FLOAT_EQ(commands[2].position.x, 116.0f);
|
||||||
|
EXPECT_FLOAT_EQ(commands[2].position.y, 362.0f);
|
||||||
|
ExpectColorEq(commands[2].color, palette.textMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIEditorPanelChromeTest, ForegroundAppendSkipsEmptyStrings) {
|
||||||
|
UIDrawList drawList("PanelChromeEmptyText");
|
||||||
|
|
||||||
|
AppendUIEditorPanelChromeForeground(
|
||||||
|
drawList,
|
||||||
|
UIRect(0.0f, 0.0f, 320.0f, 180.0f),
|
||||||
|
UIEditorPanelChromeText{});
|
||||||
|
|
||||||
|
EXPECT_EQ(drawList.GetCommandCount(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
145
tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp
Normal file
145
tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using XCEngine::UI::Widgets::kInvalidUIFlatHierarchyItemOffset;
|
||||||
|
using XCEngine::UI::Widgets::UIFlatHierarchyFindFirstVisibleChildOffset;
|
||||||
|
using XCEngine::UI::Widgets::UIFlatHierarchyFindParentOffset;
|
||||||
|
using XCEngine::UI::Widgets::UIFlatHierarchyHasChildren;
|
||||||
|
using XCEngine::UI::Widgets::UIFlatHierarchyIsVisible;
|
||||||
|
|
||||||
|
struct FlatHierarchyItem {
|
||||||
|
float depth = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(UIFlatHierarchyHelpersTest, HasChildrenAndParentResolutionTrackIndentedBranches) {
|
||||||
|
const std::array<FlatHierarchyItem, 5> items = {{
|
||||||
|
{ 0.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
{ 2.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
{ 0.0f },
|
||||||
|
}};
|
||||||
|
const std::vector<std::size_t> itemIndices = { 0u, 1u, 2u, 3u, 4u };
|
||||||
|
|
||||||
|
const auto resolveDepth = [&items](std::size_t itemIndex) {
|
||||||
|
return items[itemIndex].depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 0u, resolveDepth));
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 1u, resolveDepth));
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 2u, resolveDepth));
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 3u, resolveDepth));
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 4u, resolveDepth));
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindParentOffset(itemIndices, 0u, resolveDepth),
|
||||||
|
kInvalidUIFlatHierarchyItemOffset);
|
||||||
|
EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 1u, resolveDepth), 0u);
|
||||||
|
EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 2u, resolveDepth), 1u);
|
||||||
|
EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 3u, resolveDepth), 0u);
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindParentOffset(itemIndices, 99u, resolveDepth),
|
||||||
|
kInvalidUIFlatHierarchyItemOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIFlatHierarchyHelpersTest, VisibilityFollowsCollapsedAncestorStateAcrossDepthTransitions) {
|
||||||
|
const std::array<FlatHierarchyItem, 4> items = {{
|
||||||
|
{ 0.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
{ 2.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
}};
|
||||||
|
const std::vector<std::size_t> itemIndices = { 0u, 1u, 2u, 3u };
|
||||||
|
|
||||||
|
const auto resolveDepth = [&items](std::size_t itemIndex) {
|
||||||
|
return items[itemIndex].depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_set<std::size_t> expandedItems = { 0u };
|
||||||
|
const auto isExpanded = [&expandedItems](std::size_t itemIndex) {
|
||||||
|
return expandedItems.contains(itemIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 0u, resolveDepth, isExpanded));
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 1u, resolveDepth, isExpanded));
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded));
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 3u, resolveDepth, isExpanded));
|
||||||
|
|
||||||
|
expandedItems.insert(1u);
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded));
|
||||||
|
|
||||||
|
expandedItems.clear();
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 1u, resolveDepth, isExpanded));
|
||||||
|
EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIFlatHierarchyHelpersTest, FirstVisibleChildSkipsCollapsedDescendantsUntilExpanded) {
|
||||||
|
const std::array<FlatHierarchyItem, 4> items = {{
|
||||||
|
{ 0.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
{ 2.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
}};
|
||||||
|
const std::vector<std::size_t> itemIndices = { 0u, 1u, 2u, 3u };
|
||||||
|
|
||||||
|
const auto resolveDepth = [&items](std::size_t itemIndex) {
|
||||||
|
return items[itemIndex].depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_set<std::size_t> expandedItems = { 0u };
|
||||||
|
const auto isExpanded = [&expandedItems](std::size_t itemIndex) {
|
||||||
|
return expandedItems.contains(itemIndex);
|
||||||
|
};
|
||||||
|
const auto isVisible = [&itemIndices, &resolveDepth, &isExpanded](std::size_t itemIndex) {
|
||||||
|
for (std::size_t itemOffset = 0; itemOffset < itemIndices.size(); ++itemOffset) {
|
||||||
|
if (itemIndices[itemOffset] == itemIndex) {
|
||||||
|
return UIFlatHierarchyIsVisible(itemIndices, itemOffset, resolveDepth, isExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 0u, resolveDepth, isVisible),
|
||||||
|
1u);
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 1u, resolveDepth, isVisible),
|
||||||
|
kInvalidUIFlatHierarchyItemOffset);
|
||||||
|
|
||||||
|
expandedItems.insert(1u);
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 1u, resolveDepth, isVisible),
|
||||||
|
2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UIFlatHierarchyHelpersTest, NegativeDepthsClampToRootsForHierarchyQueries) {
|
||||||
|
const std::array<FlatHierarchyItem, 3> items = {{
|
||||||
|
{ -3.0f },
|
||||||
|
{ 1.0f },
|
||||||
|
{ -1.0f },
|
||||||
|
}};
|
||||||
|
const std::vector<std::size_t> itemIndices = { 0u, 1u, 2u };
|
||||||
|
|
||||||
|
const auto resolveDepth = [&items](std::size_t itemIndex) {
|
||||||
|
return items[itemIndex].depth;
|
||||||
|
};
|
||||||
|
const auto isExpanded = [](std::size_t itemIndex) {
|
||||||
|
return itemIndex == 0u;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 0u, resolveDepth));
|
||||||
|
EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 1u, resolveDepth), 0u);
|
||||||
|
EXPECT_EQ(
|
||||||
|
UIFlatHierarchyFindParentOffset(itemIndices, 2u, resolveDepth),
|
||||||
|
kInvalidUIFlatHierarchyItemOffset);
|
||||||
|
EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
133
tests/NewEditor/test_xcui_native_shell_layout.cpp
Normal file
133
tests/NewEditor/test_xcui_native_shell_layout.cpp
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#include "XCUIBackend/XCUINativeShellLayout.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using XCEngine::Editor::XCUIBackend::BuildXCUINativeShellCanvasRect;
|
||||||
|
using XCEngine::Editor::XCUIBackend::BuildXCUINativeShellLayout;
|
||||||
|
using XCEngine::Editor::XCUIBackend::XCUINativeShellMetrics;
|
||||||
|
using XCEngine::Editor::XCUIBackend::XCUINativeShellPanelLayout;
|
||||||
|
using XCEngine::Editor::XCUIBackend::XCUINativeShellPanelSpec;
|
||||||
|
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
|
||||||
|
using XCEngine::UI::UIPoint;
|
||||||
|
using XCEngine::UI::UIRect;
|
||||||
|
|
||||||
|
void ExpectRectEq(
|
||||||
|
const UIRect& actual,
|
||||||
|
const UIRect& expected) {
|
||||||
|
EXPECT_FLOAT_EQ(actual.x, expected.x);
|
||||||
|
EXPECT_FLOAT_EQ(actual.y, expected.y);
|
||||||
|
EXPECT_FLOAT_EQ(actual.width, expected.width);
|
||||||
|
EXPECT_FLOAT_EQ(actual.height, expected.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, CanvasRectUsesPanelHeaderAndPaddingInsets) {
|
||||||
|
const XCUINativeShellMetrics metrics = {};
|
||||||
|
const UIRect panelRect(10.0f, 20.0f, 400.0f, 300.0f);
|
||||||
|
|
||||||
|
const UIRect canvasRect = BuildXCUINativeShellCanvasRect(panelRect, metrics);
|
||||||
|
|
||||||
|
ExpectRectEq(canvasRect, UIRect(24.0f, 62.0f, 372.0f, 244.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, ActivePanelFallsBackToVisiblePanelWhenRequestedPanelIsHidden) {
|
||||||
|
const auto layout = BuildXCUINativeShellLayout(
|
||||||
|
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
|
||||||
|
{
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", false },
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
|
||||||
|
},
|
||||||
|
XCUIShellPanelId::XCUIDemo,
|
||||||
|
UIPoint(),
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
|
ASSERT_EQ(layout.panelLayouts.size(), 1u);
|
||||||
|
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUILayoutLab);
|
||||||
|
EXPECT_TRUE(layout.panelLayouts.front().active);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, TwoPanelSplitMatchesCurrentSandboxPolicy) {
|
||||||
|
const XCUINativeShellMetrics metrics = {};
|
||||||
|
const auto layout = BuildXCUINativeShellLayout(
|
||||||
|
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
|
||||||
|
{
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
|
||||||
|
},
|
||||||
|
XCUIShellPanelId::XCUIDemo,
|
||||||
|
UIPoint(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
metrics);
|
||||||
|
|
||||||
|
ExpectRectEq(layout.topBarRect, UIRect(22.0f, 22.0f, 1156.0f, 58.0f));
|
||||||
|
ExpectRectEq(layout.footerRect, UIRect(22.0f, 744.0f, 1156.0f, 34.0f));
|
||||||
|
ExpectRectEq(layout.workspaceRect, UIRect(22.0f, 98.0f, 1156.0f, 628.0f));
|
||||||
|
|
||||||
|
ASSERT_EQ(layout.panelLayouts.size(), 2u);
|
||||||
|
const XCUINativeShellPanelLayout& leftPanel = layout.panelLayouts[0];
|
||||||
|
const XCUINativeShellPanelLayout& rightPanel = layout.panelLayouts[1];
|
||||||
|
EXPECT_EQ(leftPanel.panelId, XCUIShellPanelId::XCUIDemo);
|
||||||
|
EXPECT_EQ(rightPanel.panelId, XCUIShellPanelId::XCUILayoutLab);
|
||||||
|
EXPECT_NEAR(leftPanel.panelRect.width, 693.6f, 0.001f);
|
||||||
|
EXPECT_NEAR(rightPanel.panelRect.width, 444.4f, 0.001f);
|
||||||
|
EXPECT_NEAR(rightPanel.panelRect.x, 733.6f, 0.001f);
|
||||||
|
EXPECT_FLOAT_EQ(leftPanel.panelRect.height, 628.0f);
|
||||||
|
EXPECT_FLOAT_EQ(rightPanel.panelRect.height, 628.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, PointerPressTransfersActivePanelToHoveredCanvas) {
|
||||||
|
const auto layout = BuildXCUINativeShellLayout(
|
||||||
|
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
|
||||||
|
{
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
|
||||||
|
},
|
||||||
|
XCUIShellPanelId::XCUIDemo,
|
||||||
|
UIPoint(900.0f, 200.0f),
|
||||||
|
true,
|
||||||
|
true);
|
||||||
|
|
||||||
|
ASSERT_EQ(layout.panelLayouts.size(), 2u);
|
||||||
|
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUILayoutLab);
|
||||||
|
EXPECT_FALSE(layout.panelLayouts[0].active);
|
||||||
|
EXPECT_TRUE(layout.panelLayouts[1].active);
|
||||||
|
EXPECT_TRUE(layout.panelLayouts[1].hovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, SingleVisiblePanelFillsWorkspaceAndBecomesActive) {
|
||||||
|
const auto layout = BuildXCUINativeShellLayout(
|
||||||
|
UIRect(0.0f, 0.0f, 1000.0f, 720.0f),
|
||||||
|
{
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", false },
|
||||||
|
},
|
||||||
|
XCUIShellPanelId::XCUILayoutLab,
|
||||||
|
UIPoint(),
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
|
ASSERT_EQ(layout.panelLayouts.size(), 1u);
|
||||||
|
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUIDemo);
|
||||||
|
ExpectRectEq(layout.panelLayouts[0].panelRect, layout.workspaceRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XCUINativeShellLayoutTest, UndersizedPanelStillParticipatesButReportsNotVisible) {
|
||||||
|
const auto layout = BuildXCUINativeShellLayout(
|
||||||
|
UIRect(0.0f, 0.0f, 240.0f, 400.0f),
|
||||||
|
{
|
||||||
|
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
|
||||||
|
},
|
||||||
|
XCUIShellPanelId::XCUIDemo,
|
||||||
|
UIPoint(),
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
|
ASSERT_EQ(layout.panelLayouts.size(), 1u);
|
||||||
|
EXPECT_FALSE(layout.panelLayouts[0].visible);
|
||||||
|
ExpectRectEq(layout.panelLayouts[0].panelRect, layout.workspaceRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user