Add XCUI ImGui transition backend MVP

This commit is contained in:
2026-04-04 19:22:10 +08:00
parent 0c23509e1a
commit 75ded6f630
10 changed files with 754 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
#include "panels/PanelCollection.h"
#include "panels/ProjectPanel.h"
#include "panels/SceneViewPanel.h"
#include "panels/XCUIDemoPanel.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
@@ -34,6 +35,7 @@ public:
m_panels.Emplace<InspectorPanel>();
m_panels.Emplace<ConsolePanel>();
m_projectPanel = &m_panels.Emplace<ProjectPanel>();
m_panels.Emplace<XCUIDemoPanel>();
m_dockLayoutController = std::make_unique<DockLayoutController>();
m_projectPanel->Initialize(context.GetProjectPath());

View File

@@ -0,0 +1,188 @@
#pragma once
#include <XCEngine/UI/DrawData.h>
#include <imgui.h>
#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
class ImGuiTransitionBackend {
public:
void BeginFrame() {
m_lastFlushedDrawListCount = 0;
m_lastFlushedCommandCount = 0;
m_pendingCommandCount = 0;
m_pendingDrawLists.clear();
}
void Submit(const ::XCEngine::UI::UIDrawList& drawList) {
m_pendingCommandCount += drawList.GetCommandCount();
m_pendingDrawLists.push_back(drawList);
}
void Submit(::XCEngine::UI::UIDrawList&& drawList) {
m_pendingCommandCount += drawList.GetCommandCount();
m_pendingDrawLists.push_back(std::move(drawList));
}
void Submit(const ::XCEngine::UI::UIDrawData& drawData) {
for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
Submit(drawList);
}
}
bool HasPendingDrawData() const {
return !m_pendingDrawLists.empty();
}
std::size_t GetPendingDrawListCount() const {
return m_pendingDrawLists.size();
}
std::size_t GetPendingCommandCount() const {
return m_pendingCommandCount;
}
std::size_t GetLastFlushedDrawListCount() const {
return m_lastFlushedDrawListCount;
}
std::size_t GetLastFlushedCommandCount() const {
return m_lastFlushedCommandCount;
}
bool EndFrame(ImDrawList* targetDrawList = nullptr) {
ImDrawList* drawList = targetDrawList != nullptr ? targetDrawList : ImGui::GetWindowDrawList();
if (drawList == nullptr) {
ClearPendingState();
return false;
}
std::size_t clipDepth = 0;
for (const ::XCEngine::UI::UIDrawList& pendingDrawList : m_pendingDrawLists) {
for (const ::XCEngine::UI::UIDrawCommand& command : pendingDrawList.GetCommands()) {
RenderCommand(*drawList, command, clipDepth);
}
}
while (clipDepth > 0) {
drawList->PopClipRect();
--clipDepth;
}
m_lastFlushedDrawListCount = m_pendingDrawLists.size();
m_lastFlushedCommandCount = m_pendingCommandCount;
ClearPendingState();
return true;
}
private:
static ImVec2 ToImVec2(const ::XCEngine::UI::UIPoint& point) {
return ImVec2(point.x, point.y);
}
static ImVec2 ToImVec2Min(const ::XCEngine::UI::UIRect& rect) {
return ImVec2(rect.x, rect.y);
}
static ImVec2 ToImVec2Max(const ::XCEngine::UI::UIRect& rect) {
return ImVec2(rect.x + rect.width, rect.y + rect.height);
}
static ImTextureID ToImTextureId(const ::XCEngine::UI::UITextureHandle& texture) {
return static_cast<ImTextureID>(texture.nativeHandle);
}
static ImU32 ToImU32(const ::XCEngine::UI::UIColor& color) {
const float r = (std::max)(0.0f, (std::min)(1.0f, color.r));
const float g = (std::max)(0.0f, (std::min)(1.0f, color.g));
const float b = (std::max)(0.0f, (std::min)(1.0f, color.b));
const float a = (std::max)(0.0f, (std::min)(1.0f, color.a));
return IM_COL32(
static_cast<int>(r * 255.0f),
static_cast<int>(g * 255.0f),
static_cast<int>(b * 255.0f),
static_cast<int>(a * 255.0f));
}
static void RenderCommand(
ImDrawList& drawList,
const ::XCEngine::UI::UIDrawCommand& command,
std::size_t& clipDepth) {
switch (command.type) {
case ::XCEngine::UI::UIDrawCommandType::FilledRect:
drawList.AddRectFilled(
ToImVec2Min(command.rect),
ToImVec2Max(command.rect),
ToImU32(command.color),
command.rounding);
break;
case ::XCEngine::UI::UIDrawCommandType::RectOutline:
drawList.AddRect(
ToImVec2Min(command.rect),
ToImVec2Max(command.rect),
ToImU32(command.color),
command.rounding,
0,
command.thickness > 0.0f ? command.thickness : 1.0f);
break;
case ::XCEngine::UI::UIDrawCommandType::Text:
if (!command.text.empty()) {
drawList.AddText(
nullptr,
command.fontSize > 0.0f ? command.fontSize : ImGui::GetFontSize(),
ToImVec2(command.position),
ToImU32(command.color),
command.text.c_str());
}
break;
case ::XCEngine::UI::UIDrawCommandType::Image:
if (command.texture.IsValid()) {
drawList.AddImage(
ToImTextureId(command.texture),
ToImVec2Min(command.rect),
ToImVec2Max(command.rect),
ImVec2(0.0f, 0.0f),
ImVec2(1.0f, 1.0f),
ToImU32(command.color));
}
break;
case ::XCEngine::UI::UIDrawCommandType::PushClipRect:
drawList.PushClipRect(
ToImVec2Min(command.rect),
ToImVec2Max(command.rect),
command.intersectWithCurrentClip);
++clipDepth;
break;
case ::XCEngine::UI::UIDrawCommandType::PopClipRect:
if (clipDepth > 0) {
drawList.PopClipRect();
--clipDepth;
}
break;
default:
break;
}
}
void ClearPendingState() {
m_pendingCommandCount = 0;
m_pendingDrawLists.clear();
}
std::vector<::XCEngine::UI::UIDrawList> m_pendingDrawLists;
std::size_t m_pendingCommandCount = 0;
std::size_t m_lastFlushedDrawListCount = 0;
std::size_t m_lastFlushedCommandCount = 0;
};
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,140 @@
#include "XCUIDemoPanel.h"
#include "UI/UI.h"
#include <XCEngine/UI/DrawData.h>
#include <imgui.h>
namespace {
::XCEngine::UI::UIDrawList BuildXCUIDemoDrawList(
const ImVec2& canvasMin,
const ImVec2& canvasSize) {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
UIDrawList drawList("XCUI Transition Demo");
drawList.PushClipRect(UIRect(canvasMin.x, canvasMin.y, canvasSize.x, canvasSize.y));
drawList.AddFilledRect(
UIRect(canvasMin.x, canvasMin.y, canvasSize.x, canvasSize.y),
UIColor(0.09f, 0.11f, 0.14f, 1.0f),
10.0f);
drawList.AddRectOutline(
UIRect(canvasMin.x, canvasMin.y, canvasSize.x, canvasSize.y),
UIColor(0.32f, 0.40f, 0.49f, 1.0f),
1.0f,
10.0f);
const float cardX = canvasMin.x + 18.0f;
const float cardY = canvasMin.y + 18.0f;
const float cardWidth = (canvasSize.x - 36.0f) > 120.0f ? (canvasSize.x - 36.0f) : 120.0f;
drawList.AddFilledRect(
UIRect(cardX, cardY, cardWidth, 56.0f),
UIColor(0.15f, 0.20f, 0.27f, 1.0f),
8.0f);
drawList.AddFilledRect(
UIRect(cardX + 14.0f, cardY + 16.0f, 10.0f, 10.0f),
UIColor(0.18f, 0.76f, 0.58f, 1.0f),
5.0f);
drawList.AddText(
UIPoint(cardX + 32.0f, cardY + 12.0f),
"XCUI Transition Backend",
UIColor(0.96f, 0.97f, 0.99f, 1.0f),
18.0f);
drawList.AddText(
UIPoint(cardX + 32.0f, cardY + 33.0f),
"This panel is generated by XCUI draw commands, then flushed through ImGui.",
UIColor(0.74f, 0.79f, 0.86f, 1.0f),
13.0f);
const float statY = cardY + 76.0f;
drawList.AddFilledRect(
UIRect(cardX, statY, 140.0f, 74.0f),
UIColor(0.11f, 0.16f, 0.22f, 1.0f),
8.0f);
drawList.AddFilledRect(
UIRect(cardX + 158.0f, statY, 180.0f, 74.0f),
UIColor(0.11f, 0.16f, 0.22f, 1.0f),
8.0f);
drawList.AddText(
UIPoint(cardX + 14.0f, statY + 12.0f),
"Primitive Set",
UIColor(0.68f, 0.75f, 0.84f, 1.0f),
13.0f);
drawList.AddText(
UIPoint(cardX + 14.0f, statY + 36.0f),
"Rect / Border / Text / Image",
UIColor(0.95f, 0.97f, 0.99f, 1.0f),
15.0f);
drawList.AddText(
UIPoint(cardX + 172.0f, statY + 12.0f),
"Backend Flow",
UIColor(0.68f, 0.75f, 0.84f, 1.0f),
13.0f);
drawList.AddText(
UIPoint(cardX + 172.0f, statY + 36.0f),
"UIDrawList -> ImGuiTransitionBackend -> ImDrawList",
UIColor(0.95f, 0.97f, 0.99f, 1.0f),
15.0f);
const float footerY = canvasMin.y + canvasSize.y - 54.0f;
if (footerY > statY + 90.0f) {
drawList.AddFilledRect(
UIRect(cardX, footerY, cardWidth, 36.0f),
UIColor(0.14f, 0.17f, 0.22f, 1.0f),
8.0f);
drawList.AddText(
UIPoint(cardX + 14.0f, footerY + 10.0f),
"Subplan-05 MVP: XCUI owns draw data, ImGui is only the transition renderer.",
UIColor(0.79f, 0.84f, 0.90f, 1.0f),
13.0f);
}
drawList.PopClipRect();
return drawList;
}
} // namespace
namespace XCEngine {
namespace Editor {
XCUIDemoPanel::XCUIDemoPanel()
: Panel("XCUI Demo") {
}
void XCUIDemoPanel::Render() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
UI::PanelWindowScope panel(m_name.c_str());
ImGui::PopStyleVar();
if (!panel.IsOpen()) {
return;
}
UI::PanelContentScope content("XCUIDemoContent", ImVec2(0.0f, 0.0f));
if (!content.IsOpen()) {
return;
}
const ImVec2 availableSize = ImGui::GetContentRegionAvail();
if (availableSize.x <= 1.0f || availableSize.y <= 1.0f) {
ImGui::Dummy(ImVec2(0.0f, 0.0f));
return;
}
const ImVec2 canvasMin = ImGui::GetCursorScreenPos();
ImGui::InvisibleButton("##XCUIDemoCanvas", availableSize);
::XCEngine::UI::UIDrawData drawData = {};
drawData.AddDrawList(BuildXCUIDemoDrawList(canvasMin, availableSize));
m_backend.BeginFrame();
m_backend.Submit(drawData);
m_backend.EndFrame(ImGui::GetWindowDrawList());
}
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Panel.h"
#include "XCUIBackend/ImGuiTransitionBackend.h"
namespace XCEngine {
namespace Editor {
class XCUIDemoPanel : public Panel {
public:
XCUIDemoPanel();
void Render() override;
private:
XCUIBackend::ImGuiTransitionBackend m_backend;
};
} // namespace Editor
} // namespace XCEngine