Unlink XCNewEditor default shell from ImGui compat slice

This commit is contained in:
2026-04-05 16:32:43 +08:00
parent 712cc79c44
commit 74b1280aa6
12 changed files with 1128 additions and 57 deletions

View File

@@ -44,6 +44,7 @@ endif()
set(NEW_EDITOR_SOURCES
src/main.cpp
src/Application.cpp
src/ApplicationDefaultShell.cpp
src/panels/Panel.cpp
src/Rendering/MainWindowBackdropPass.cpp
src/Rendering/MainWindowNativeBackdropRenderer.cpp
@@ -156,7 +157,6 @@ target_link_libraries(XCNewEditorImGuiCompat PRIVATE
target_link_libraries(${PROJECT_NAME} PRIVATE
XCEngine
XCNewEditorImGuiCompat
d3d12.lib
dxgi.lib
user32

View 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

View File

@@ -10,6 +10,7 @@
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
@@ -556,23 +557,17 @@ bool HasTreeItemChildren(const RuntimeBuildContext& state, std::size_t nodeIndex
return false;
}
const float indentLevel = ResolveTreeIndentLevel(node);
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
if (siblingIt == parent.children.end()) {
return false;
}
for (auto it = siblingIt + 1; it != parent.children.end(); ++it) {
const LayoutNode& sibling = state.nodes[*it];
const float siblingIndentLevel = ResolveTreeIndentLevel(sibling);
if (siblingIndentLevel <= indentLevel) {
break;
}
return true;
}
return false;
return UIWidgets::UIFlatHierarchyHasChildren(
std::span<const std::size_t>(parent.children),
static_cast<std::size_t>(siblingIt - parent.children.begin()),
[&state](std::size_t siblingIndex) {
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
});
}
bool IsNodeCollapsible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
@@ -626,32 +621,20 @@ bool IsNodeVisible(const RuntimeBuildContext& state, std::size_t nodeIndex) {
return true;
}
float requiredAncestorIndent = ResolveTreeIndentLevel(node);
if (requiredAncestorIndent <= 0.0f) {
return true;
}
const auto siblingIt = std::find(parent.children.begin(), parent.children.end(), nodeIndex);
if (siblingIt == parent.children.end()) {
return true;
}
const std::size_t siblingOffset =
static_cast<std::size_t>(siblingIt - parent.children.begin());
for (std::size_t offset = siblingOffset; offset > 0u && requiredAncestorIndent > 0.0f; --offset) {
const std::size_t siblingIndex = parent.children[offset - 1u];
const LayoutNode& sibling = state.nodes[siblingIndex];
const float siblingIndent = ResolveTreeIndentLevel(sibling);
if (siblingIndent < requiredAncestorIndent) {
if (!IsNodeExpanded(state, siblingIndex)) {
return false;
}
requiredAncestorIndent = siblingIndent;
}
}
return true;
return UIWidgets::UIFlatHierarchyIsVisible(
std::span<const std::size_t>(parent.children),
static_cast<std::size_t>(siblingIt - parent.children.begin()),
[&state](std::size_t siblingIndex) {
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
},
[&state](std::size_t siblingIndex) {
return IsNodeExpanded(state, siblingIndex);
});
}
std::vector<std::size_t> CollectVisibleChildren(
@@ -961,14 +944,17 @@ std::size_t FindTreeParentItemIndex(
return kInvalidIndex;
}
for (auto it = siblingIt; it != treeView.children.begin();) {
--it;
if (ResolveTreeIndentLevel(state.nodes[*it]) < indentLevel) {
return *it;
}
const std::size_t parentOffset = UIWidgets::UIFlatHierarchyFindParentOffset(
std::span<const std::size_t>(treeView.children),
static_cast<std::size_t>(siblingIt - treeView.children.begin()),
[&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(
@@ -981,25 +967,25 @@ std::size_t FindFirstTreeChildItemIndex(
const LayoutNode& node = state.nodes[nodeIndex];
const LayoutNode& treeView = state.nodes[node.parentIndex];
const float indentLevel = ResolveTreeIndentLevel(node);
const auto siblingIt = std::find(treeView.children.begin(), treeView.children.end(), nodeIndex);
if (siblingIt == treeView.children.end()) {
return kInvalidIndex;
}
for (auto it = siblingIt + 1; it != treeView.children.end(); ++it) {
const std::size_t candidateIndex = *it;
const float candidateIndent = ResolveTreeIndentLevel(state.nodes[candidateIndex]);
if (candidateIndent <= indentLevel) {
break;
}
if (IsNodeVisible(state, candidateIndex)) {
return candidateIndex;
}
const std::size_t childOffset = UIWidgets::UIFlatHierarchyFindFirstVisibleChildOffset(
std::span<const std::size_t>(treeView.children),
static_cast<std::size_t>(siblingIt - treeView.children.begin()),
[&state](std::size_t siblingIndex) {
return ResolveTreeIndentLevel(state.nodes[siblingIndex]);
},
[&state](std::size_t siblingIndex) {
return IsNodeVisible(state, siblingIndex);
});
if (childOffset == UIWidgets::kInvalidUIFlatHierarchyItemOffset) {
return kInvalidIndex;
}
return kInvalidIndex;
return treeView.children[childOffset];
}
bool HandleKeyboardExpand(RuntimeBuildContext& state) {

View 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

View File

@@ -6,6 +6,7 @@
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
namespace XCEngine {
@@ -14,13 +15,15 @@ namespace XCUIBackend {
enum class XCUIPanelCanvasHostBackend : std::uint8_t {
Null = 0,
ImGui
ImGui,
Native
};
struct XCUIPanelCanvasHostCapabilities {
bool supportsPointerHitTesting = false;
bool supportsHostedSurfaceImages = false;
bool supportsPrimitiveOverlays = false;
bool supportsExternallyDrivenSession = false;
};
struct XCUIPanelCanvasRequest {
@@ -46,6 +49,20 @@ struct XCUIPanelCanvasSession {
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(
const XCUIPanelCanvasRequest& request,
const char* fallback = "XCUIPanelCanvasHost") {
@@ -99,6 +116,10 @@ public:
const ::XCEngine::UI::UIColor& color,
float fontSize = 0.0f) = 0;
virtual void EndCanvas() = 0;
virtual bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const {
outSnapshot = {};
return false;
}
};
std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost();