Extract XCUI selection model and layout lab click selection

This commit is contained in:
2026-04-05 07:03:51 +08:00
parent d46dcbfa9e
commit 646e5855ce
10 changed files with 202 additions and 15 deletions

View File

@@ -9,6 +9,7 @@
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIEditorCollectionPrimitives.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <algorithm>
#include <cstdlib>
@@ -77,6 +78,7 @@ struct RuntimeBuildContext {
std::vector<LayoutNode> nodes = {};
std::unordered_map<std::string, std::size_t> nodeIndexById = {};
std::unordered_map<std::string, UIRect> rectsById = {};
UIWidgets::UISelectionModel selectionModel = {};
bool documentsReady = false;
std::string statusMessage = {};
XCUIAssetDocumentSource documentSource = XCUIAssetDocumentSource(
@@ -752,6 +754,7 @@ void DrawNode(
bodyFont);
}
} else if (node.tagName == "Card") {
const bool selected = state.selectionModel.IsSelected(node.id);
const Color cardColor = ResolveColorToken(
state.theme,
node.tone == "accent"
@@ -799,17 +802,25 @@ void DrawNode(
UIColor(1.0f, 0.82f, 0.45f, 1.0f),
2.0f,
rounding);
} else if (selected) {
drawList.AddRectOutline(
node.rect,
ToUIColor(ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))),
2.0f,
rounding);
}
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::TreeItem ||
primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::ListItem) {
const bool hovered = node.id == hoveredId;
const Color rowColor = hovered
? ResolveColorToken(state.theme, "color.card.alt", Color(0.20f, 0.27f, 0.34f, 1.0f))
: ResolveColorToken(state.theme, "color.card", Color(0.12f, 0.17f, 0.23f, 1.0f));
const Color borderColor = ResolveColorToken(
state.theme,
"color.border",
Color(0.24f, 0.34f, 0.43f, 1.0f));
const bool selected = state.selectionModel.IsSelected(node.id);
const Color rowColor = selected
? ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))
: (hovered
? ResolveColorToken(state.theme, "color.card.alt", Color(0.20f, 0.27f, 0.34f, 1.0f))
: ResolveColorToken(state.theme, "color.card", Color(0.12f, 0.17f, 0.23f, 1.0f)));
const Color borderColor = selected
? ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))
: ResolveColorToken(state.theme, "color.border", Color(0.24f, 0.34f, 0.43f, 1.0f));
const Color textColor = ResolveColorToken(
state.theme,
"color.text",
@@ -848,6 +859,7 @@ void DrawNode(
}
} else if (primitiveKind == UIWidgets::UIEditorCollectionPrimitiveKind::FieldRow) {
const bool hovered = node.id == hoveredId;
const bool selected = state.selectionModel.IsSelected(node.id);
const Color textColor = ResolveColorToken(
state.theme,
"color.text",
@@ -856,7 +868,7 @@ void DrawNode(
state.theme,
"color.text.muted",
Color(0.72f, 0.79f, 0.86f, 1.0f));
const Color lineColor = hovered
const Color lineColor = selected || hovered
? ResolveColorToken(state.theme, "color.accent", Color(0.30f, 0.46f, 0.58f, 1.0f))
: ResolveColorToken(state.theme, "color.border", Color(0.24f, 0.34f, 0.43f, 1.0f));
const float inset = ResolveFloatToken(state.theme, "space.cardInset", 12.0f);
@@ -955,6 +967,7 @@ bool XCUILayoutLabRuntime::ReloadDocuments() {
state.nodes.clear();
state.nodeIndexById.clear();
state.rectsById.clear();
state.selectionModel.ClearSelection();
state.documentSource.SetPathSet(XCUIAssetDocumentSource::MakeLayoutLabPathSet());
if (!state.documentSource.Reload()) {
@@ -1027,13 +1040,24 @@ const XCUILayoutLabFrameResult& XCUILayoutLabRuntime::Update(const XCUILayoutLab
state.rectsById[state.nodes[0].id] = state.nodes[0].rect;
}
std::size_t hoveredIndex = kInvalidIndex;
if (input.pointerInside) {
const std::size_t hoveredIndex = HitTest(state, input.pointerPosition);
hoveredIndex = HitTest(state, input.pointerPosition);
if (hoveredIndex != kInvalidIndex) {
state.frameResult.stats.hoveredElementId = state.nodes[hoveredIndex].id;
}
}
if (input.pointerPressed) {
if (hoveredIndex != kInvalidIndex) {
state.selectionModel.SetSelection(state.nodes[hoveredIndex].id);
} else {
state.selectionModel.ClearSelection();
}
}
state.frameResult.stats.selectedElementId = state.selectionModel.GetSelectedId();
UIDrawList drawList("XCUI Layout Lab");
drawList.PushClipRect(input.canvasRect);
DrawNode(state, 0u, drawList, state.frameResult.stats.hoveredElementId);

View File

@@ -17,6 +17,7 @@ struct XCUILayoutLabInputState {
UI::UIRect canvasRect = {};
UI::UIPoint pointerPosition = {};
bool pointerInside = false;
bool pointerPressed = false;
};
struct XCUILayoutLabFrameStats {
@@ -47,6 +48,7 @@ struct XCUILayoutLabFrameStats {
std::size_t propertySectionCount = 0;
std::size_t fieldRowCount = 0;
std::string hoveredElementId = {};
std::string selectedElementId = {};
};
struct XCUILayoutLabFrameResult {

View File

@@ -241,6 +241,7 @@ void XCUILayoutLabPanel::Render() {
input.pointerPosition = UI::UIPoint(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y);
input.pointerInside = validCanvas && ImGui::IsItemHovered();
}
input.pointerPressed = input.pointerInside && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& frame = m_runtime.Update(input);
@@ -337,17 +338,19 @@ void XCUILayoutLabPanel::Render() {
"Native note: %s",
stats.nativeOverlayStatusMessage.empty() ? "none" : stats.nativeOverlayStatusMessage.c_str());
ImGui::Text(
"Hovered: %s | canvas: %.0f x %.0f",
"Hovered: %s | Selected: %s | canvas: %.0f x %.0f",
stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(),
stats.selectedElementId.empty() ? "none" : stats.selectedElementId.c_str(),
input.canvasRect.width,
input.canvasRect.height);
ImGui::SeparatorText("Input");
ImGui::Text(
"Pointer: %.0f, %.0f | inside %s",
"Pointer: %.0f, %.0f | inside %s | pressed %s",
input.pointerPosition.x,
input.pointerPosition.y,
input.pointerInside ? "yes" : "no");
input.pointerInside ? "yes" : "no",
input.pointerPressed ? "yes" : "no");
ImGui::EndChild();
ImGui::End();