Add editor tree view widget contract

This commit is contained in:
2026-04-07 14:41:01 +08:00
parent 442565f176
commit 0308be1483
14 changed files with 1916 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ add_library(XCUIEditorLib STATIC
src/Core/UIEditorShellCompose.cpp
src/Core/UIEditorShellInteraction.cpp
src/Core/UIEditorShortcutManager.cpp
src/Core/UIEditorTreeViewInteraction.cpp
src/Core/UIEditorViewportInputBridge.cpp
src/Core/UIEditorViewportShell.cpp
src/Core/UIEditorWorkspaceCompose.cpp
@@ -39,6 +40,7 @@ add_library(XCUIEditorLib STATIC
src/Widgets/UIEditorPanelFrame.cpp
src/Widgets/UIEditorStatusBar.cpp
src/Widgets/UIEditorTabStrip.cpp
src/Widgets/UIEditorTreeView.cpp
src/Widgets/UIEditorViewportSlot.cpp
)

View File

@@ -0,0 +1,44 @@
#pragma once
#include <XCEditor/Widgets/UIEditorTreeView.h>
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorTreeViewInteractionState {
Widgets::UIEditorTreeViewState treeViewState = {};
::XCEngine::UI::UIPoint pointerPosition = {};
bool hasPointerPosition = false;
};
struct UIEditorTreeViewInteractionResult {
bool consumed = false;
bool selectionChanged = false;
bool expansionChanged = false;
bool secondaryClicked = false;
Widgets::UIEditorTreeViewHitTarget hitTarget = {};
std::string selectedItemId = {};
std::string toggledItemId = {};
};
struct UIEditorTreeViewInteractionFrame {
Widgets::UIEditorTreeViewLayout layout = {};
UIEditorTreeViewInteractionResult result = {};
};
UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction(
UIEditorTreeViewInteractionState& state,
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorTreeViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,145 @@
#pragma once
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Widgets {
inline constexpr std::size_t UIEditorTreeViewInvalidIndex = static_cast<std::size_t>(-1);
enum class UIEditorTreeViewHitTargetKind : std::uint8_t {
None = 0,
Row,
Disclosure
};
struct UIEditorTreeViewItem {
std::string itemId = {};
std::string label = {};
std::uint32_t depth = 0u;
bool forceLeaf = false;
float desiredHeight = 0.0f;
};
struct UIEditorTreeViewState {
std::string hoveredItemId = {};
bool focused = false;
};
struct UIEditorTreeViewMetrics {
float rowHeight = 28.0f;
float rowGap = 2.0f;
float horizontalPadding = 8.0f;
float indentWidth = 18.0f;
float disclosureExtent = 12.0f;
float disclosureLabelGap = 6.0f;
float labelInsetY = 6.0f;
float cornerRounding = 6.0f;
float borderThickness = 1.0f;
float focusedBorderThickness = 2.0f;
};
struct UIEditorTreeViewPalette {
::XCEngine::UI::UIColor surfaceColor =
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
::XCEngine::UI::UIColor borderColor =
::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f);
::XCEngine::UI::UIColor focusedBorderColor =
::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f);
::XCEngine::UI::UIColor rowHoverColor =
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f);
::XCEngine::UI::UIColor rowSelectedColor =
::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f);
::XCEngine::UI::UIColor rowSelectedFocusedColor =
::XCEngine::UI::UIColor(0.40f, 0.40f, 0.40f, 1.0f);
::XCEngine::UI::UIColor disclosureColor =
::XCEngine::UI::UIColor(0.74f, 0.74f, 0.74f, 1.0f);
::XCEngine::UI::UIColor textColor =
::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f);
};
struct UIEditorTreeViewLayout {
::XCEngine::UI::UIRect bounds = {};
std::vector<std::size_t> visibleItemIndices = {};
std::vector<::XCEngine::UI::UIRect> rowRects = {};
std::vector<::XCEngine::UI::UIRect> disclosureRects = {};
std::vector<::XCEngine::UI::UIRect> labelRects = {};
std::vector<bool> itemHasChildren = {};
std::vector<bool> itemExpanded = {};
};
struct UIEditorTreeViewHitTarget {
UIEditorTreeViewHitTargetKind kind = UIEditorTreeViewHitTargetKind::None;
std::size_t visibleIndex = UIEditorTreeViewInvalidIndex;
std::size_t itemIndex = UIEditorTreeViewInvalidIndex;
};
bool IsUIEditorTreeViewPointInside(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point);
bool DoesUIEditorTreeViewItemHaveChildren(
const std::vector<UIEditorTreeViewItem>& items,
std::size_t itemIndex);
std::size_t FindUIEditorTreeViewItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
std::string_view itemId);
std::size_t FindUIEditorTreeViewParentItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
std::size_t itemIndex);
std::vector<std::size_t> CollectUIEditorTreeViewVisibleItemIndices(
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel);
std::size_t FindUIEditorTreeViewFirstVisibleChildItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
std::size_t itemIndex);
UIEditorTreeViewLayout BuildUIEditorTreeViewLayout(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewMetrics& metrics = {});
UIEditorTreeViewHitTarget HitTestUIEditorTreeView(
const UIEditorTreeViewLayout& layout,
const ::XCEngine::UI::UIPoint& point);
void AppendUIEditorTreeViewBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTreeViewLayout& layout,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const UIEditorTreeViewState& state,
const UIEditorTreeViewPalette& palette = {},
const UIEditorTreeViewMetrics& metrics = {});
void AppendUIEditorTreeViewForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTreeViewLayout& layout,
const std::vector<UIEditorTreeViewItem>& items,
const UIEditorTreeViewPalette& palette = {},
const UIEditorTreeViewMetrics& metrics = {});
void AppendUIEditorTreeView(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewState& state,
const UIEditorTreeViewPalette& palette = {},
const UIEditorTreeViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor::Widgets

View File

@@ -0,0 +1,192 @@
#include <XCEditor/Core/UIEditorTreeViewInteraction.h>
namespace XCEngine::UI::Editor {
namespace {
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPointerButton;
using Widgets::BuildUIEditorTreeViewLayout;
using Widgets::DoesUIEditorTreeViewItemHaveChildren;
using Widgets::HitTestUIEditorTreeView;
using Widgets::IsUIEditorTreeViewPointInside;
using Widgets::UIEditorTreeViewHitTarget;
using Widgets::UIEditorTreeViewHitTargetKind;
bool ShouldUsePointerPosition(const UIInputEvent& event) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
return true;
default:
return false;
}
}
void SyncHoverTarget(
UIEditorTreeViewInteractionState& state,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items) {
state.treeViewState.hoveredItemId.clear();
if (!state.hasPointerPosition) {
return;
}
const UIEditorTreeViewHitTarget hitTarget =
HitTestUIEditorTreeView(layout, state.pointerPosition);
if (hitTarget.itemIndex < items.size()) {
state.treeViewState.hoveredItemId = items[hitTarget.itemIndex].itemId;
}
}
} // namespace
UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction(
UIEditorTreeViewInteractionState& state,
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<UIInputEvent>& inputEvents,
const Widgets::UIEditorTreeViewMetrics& metrics) {
Widgets::UIEditorTreeViewLayout layout =
BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics);
SyncHoverTarget(state, layout, items);
UIEditorTreeViewInteractionResult interactionResult = {};
for (const UIInputEvent& event : inputEvents) {
if (ShouldUsePointerPosition(event)) {
state.pointerPosition = event.position;
state.hasPointerPosition = true;
} else if (event.type == UIInputEventType::PointerLeave) {
state.hasPointerPosition = false;
}
UIEditorTreeViewInteractionResult eventResult = {};
switch (event.type) {
case UIInputEventType::FocusGained:
state.treeViewState.focused = true;
break;
case UIInputEventType::FocusLost:
state.treeViewState.focused = false;
state.hasPointerPosition = false;
state.treeViewState.hoveredItemId.clear();
break;
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
break;
case UIInputEventType::PointerLeave:
state.treeViewState.hoveredItemId.clear();
break;
case UIInputEventType::PointerButtonDown: {
const UIEditorTreeViewHitTarget hitTarget =
state.hasPointerPosition
? HitTestUIEditorTreeView(layout, state.pointerPosition)
: UIEditorTreeViewHitTarget {};
eventResult.hitTarget = hitTarget;
if ((event.pointerButton == UIPointerButton::Left ||
event.pointerButton == UIPointerButton::Right) &&
hitTarget.kind != UIEditorTreeViewHitTargetKind::None) {
state.treeViewState.focused = true;
eventResult.consumed = true;
} else if (event.pointerButton == UIPointerButton::Left &&
(!state.hasPointerPosition ||
!IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition))) {
state.treeViewState.focused = false;
}
break;
}
case UIInputEventType::PointerButtonUp: {
const UIEditorTreeViewHitTarget hitTarget =
state.hasPointerPosition
? HitTestUIEditorTreeView(layout, state.pointerPosition)
: UIEditorTreeViewHitTarget {};
eventResult.hitTarget = hitTarget;
const bool insideTree =
state.hasPointerPosition &&
IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition);
if (hitTarget.itemIndex >= items.size()) {
if (event.pointerButton == UIPointerButton::Left && insideTree) {
eventResult.consumed = true;
state.treeViewState.focused = true;
} else if (event.pointerButton == UIPointerButton::Left) {
state.treeViewState.focused = false;
}
break;
}
const Widgets::UIEditorTreeViewItem& item = items[hitTarget.itemIndex];
if (event.pointerButton == UIPointerButton::Left) {
if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure &&
DoesUIEditorTreeViewItemHaveChildren(items, hitTarget.itemIndex)) {
eventResult.expansionChanged =
expansionModel.ToggleExpanded(item.itemId);
eventResult.toggledItemId = item.itemId;
eventResult.consumed = true;
state.treeViewState.focused = true;
} else if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
eventResult.selectionChanged =
selectionModel.SetSelection(item.itemId);
eventResult.selectedItemId = item.itemId;
eventResult.consumed = true;
state.treeViewState.focused = true;
}
} else if (event.pointerButton == UIPointerButton::Right &&
(hitTarget.kind == UIEditorTreeViewHitTargetKind::Row ||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) {
eventResult.selectionChanged =
selectionModel.SetSelection(item.itemId);
eventResult.selectedItemId = item.itemId;
eventResult.secondaryClicked = true;
eventResult.consumed = true;
state.treeViewState.focused = true;
}
break;
}
default:
break;
}
layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics);
SyncHoverTarget(state, layout, items);
if (eventResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None &&
state.hasPointerPosition) {
eventResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition);
}
if (eventResult.consumed ||
eventResult.selectionChanged ||
eventResult.expansionChanged ||
eventResult.secondaryClicked ||
eventResult.hitTarget.kind != UIEditorTreeViewHitTargetKind::None ||
!eventResult.selectedItemId.empty() ||
!eventResult.toggledItemId.empty()) {
interactionResult = std::move(eventResult);
}
}
layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics);
SyncHoverTarget(state, layout, items);
if (interactionResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None &&
state.hasPointerPosition) {
interactionResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition);
}
return {
std::move(layout),
std::move(interactionResult)
};
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,314 @@
#include <XCEditor/Widgets/UIEditorTreeView.h>
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
#include <algorithm>
#include <numeric>
namespace XCEngine::UI::Editor::Widgets {
namespace {
float ClampNonNegative(float value) {
return (std::max)(value, 0.0f);
}
std::vector<std::size_t> BuildItemOffsets(std::size_t count) {
std::vector<std::size_t> offsets(count);
std::iota(offsets.begin(), offsets.end(), 0u);
return offsets;
}
float ResolveTreeViewRowHeight(
const UIEditorTreeViewItem& item,
const UIEditorTreeViewMetrics& metrics) {
return item.desiredHeight > 0.0f ? item.desiredHeight : metrics.rowHeight;
}
::XCEngine::UI::UIPoint ResolveDisclosureGlyphPosition(
const ::XCEngine::UI::UIRect& rect,
float insetY) {
return ::XCEngine::UI::UIPoint(rect.x + 2.0f, rect.y + insetY - 1.0f);
}
} // namespace
bool IsUIEditorTreeViewPointInside(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
bool DoesUIEditorTreeViewItemHaveChildren(
const std::vector<UIEditorTreeViewItem>& items,
std::size_t itemIndex) {
if (itemIndex >= items.size() || items[itemIndex].forceLeaf) {
return false;
}
const std::vector<std::size_t> itemOffsets = BuildItemOffsets(items.size());
return ::XCEngine::UI::Widgets::UIFlatHierarchyHasChildren(
itemOffsets,
itemIndex,
[&](std::size_t offset) {
return items[offset].depth;
});
}
std::size_t FindUIEditorTreeViewItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
std::string_view itemId) {
for (std::size_t itemIndex = 0u; itemIndex < items.size(); ++itemIndex) {
if (items[itemIndex].itemId == itemId) {
return itemIndex;
}
}
return UIEditorTreeViewInvalidIndex;
}
std::size_t FindUIEditorTreeViewParentItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
std::size_t itemIndex) {
const std::vector<std::size_t> itemOffsets = BuildItemOffsets(items.size());
const std::size_t parentOffset = ::XCEngine::UI::Widgets::UIFlatHierarchyFindParentOffset(
itemOffsets,
itemIndex,
[&](std::size_t offset) {
return items[offset].depth;
});
return parentOffset != ::XCEngine::UI::Widgets::kInvalidUIFlatHierarchyItemOffset
? parentOffset
: UIEditorTreeViewInvalidIndex;
}
std::vector<std::size_t> CollectUIEditorTreeViewVisibleItemIndices(
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel) {
std::vector<std::size_t> visibleItemIndices = {};
if (items.empty()) {
return visibleItemIndices;
}
const std::vector<std::size_t> itemOffsets = BuildItemOffsets(items.size());
for (std::size_t itemOffset = 0u; itemOffset < items.size(); ++itemOffset) {
const bool visible = ::XCEngine::UI::Widgets::UIFlatHierarchyIsVisible(
itemOffsets,
itemOffset,
[&](std::size_t offset) {
return items[offset].depth;
},
[&](std::size_t offset) {
return expansionModel.IsExpanded(items[offset].itemId);
});
if (visible) {
visibleItemIndices.push_back(itemOffset);
}
}
return visibleItemIndices;
}
std::size_t FindUIEditorTreeViewFirstVisibleChildItemIndex(
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
std::size_t itemIndex) {
if (itemIndex >= items.size()) {
return UIEditorTreeViewInvalidIndex;
}
const std::vector<std::size_t> itemOffsets = BuildItemOffsets(items.size());
const std::size_t childOffset =
::XCEngine::UI::Widgets::UIFlatHierarchyFindFirstVisibleChildOffset(
itemOffsets,
itemIndex,
[&](std::size_t offset) {
return items[offset].depth;
},
[&](std::size_t offset) {
return ::XCEngine::UI::Widgets::UIFlatHierarchyIsVisible(
itemOffsets,
offset,
[&](std::size_t visibleOffset) {
return items[visibleOffset].depth;
},
[&](std::size_t visibleOffset) {
return expansionModel.IsExpanded(items[visibleOffset].itemId);
});
});
return childOffset != ::XCEngine::UI::Widgets::kInvalidUIFlatHierarchyItemOffset
? childOffset
: UIEditorTreeViewInvalidIndex;
}
UIEditorTreeViewLayout BuildUIEditorTreeViewLayout(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewMetrics& metrics) {
UIEditorTreeViewLayout layout = {};
layout.bounds = ::XCEngine::UI::UIRect(
bounds.x,
bounds.y,
ClampNonNegative(bounds.width),
ClampNonNegative(bounds.height));
layout.visibleItemIndices = CollectUIEditorTreeViewVisibleItemIndices(items, expansionModel);
layout.rowRects.reserve(layout.visibleItemIndices.size());
layout.disclosureRects.reserve(layout.visibleItemIndices.size());
layout.labelRects.reserve(layout.visibleItemIndices.size());
layout.itemHasChildren.reserve(layout.visibleItemIndices.size());
layout.itemExpanded.reserve(layout.visibleItemIndices.size());
float rowY = layout.bounds.y;
for (std::size_t visibleOffset = 0u;
visibleOffset < layout.visibleItemIndices.size();
++visibleOffset) {
const std::size_t itemIndex = layout.visibleItemIndices[visibleOffset];
const UIEditorTreeViewItem& item = items[itemIndex];
const float rowHeight = ResolveTreeViewRowHeight(item, metrics);
const bool hasChildren = DoesUIEditorTreeViewItemHaveChildren(items, itemIndex);
const bool expanded = hasChildren && expansionModel.IsExpanded(item.itemId);
const ::XCEngine::UI::UIRect rowRect(
layout.bounds.x,
rowY,
layout.bounds.width,
rowHeight);
const float contentX =
rowRect.x + metrics.horizontalPadding + static_cast<float>(item.depth) * metrics.indentWidth;
const ::XCEngine::UI::UIRect disclosureRect(
contentX,
rowRect.y + (rowRect.height - metrics.disclosureExtent) * 0.5f,
metrics.disclosureExtent,
metrics.disclosureExtent);
const ::XCEngine::UI::UIRect labelRect(
disclosureRect.x + metrics.disclosureExtent + metrics.disclosureLabelGap,
rowRect.y,
(rowRect.x + rowRect.width) -
(disclosureRect.x + metrics.disclosureExtent + metrics.disclosureLabelGap) -
metrics.horizontalPadding,
rowRect.height);
layout.rowRects.push_back(rowRect);
layout.disclosureRects.push_back(disclosureRect);
layout.labelRects.push_back(labelRect);
layout.itemHasChildren.push_back(hasChildren);
layout.itemExpanded.push_back(expanded);
rowY += rowHeight + metrics.rowGap;
}
return layout;
}
UIEditorTreeViewHitTarget HitTestUIEditorTreeView(
const UIEditorTreeViewLayout& layout,
const ::XCEngine::UI::UIPoint& point) {
for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) {
if (!IsUIEditorTreeViewPointInside(layout.rowRects[visibleOffset], point)) {
continue;
}
UIEditorTreeViewHitTarget target = {};
target.visibleIndex = visibleOffset;
target.itemIndex = layout.visibleItemIndices[visibleOffset];
target.kind =
layout.itemHasChildren[visibleOffset] &&
IsUIEditorTreeViewPointInside(layout.disclosureRects[visibleOffset], point)
? UIEditorTreeViewHitTargetKind::Disclosure
: UIEditorTreeViewHitTargetKind::Row;
return target;
}
return {};
}
void AppendUIEditorTreeViewBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTreeViewLayout& layout,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const UIEditorTreeViewState& state,
const UIEditorTreeViewPalette& palette,
const UIEditorTreeViewMetrics& metrics) {
drawList.AddFilledRect(layout.bounds, palette.surfaceColor, metrics.cornerRounding);
drawList.AddRectOutline(
layout.bounds,
state.focused ? palette.focusedBorderColor : palette.borderColor,
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
metrics.cornerRounding);
for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) {
const UIEditorTreeViewItem& item = items[layout.visibleItemIndices[visibleOffset]];
const bool selected = selectionModel.IsSelected(item.itemId);
const bool hovered = state.hoveredItemId == item.itemId;
if (!selected && !hovered) {
continue;
}
const ::XCEngine::UI::UIColor rowColor =
selected
? (state.focused ? palette.rowSelectedFocusedColor : palette.rowSelectedColor)
: palette.rowHoverColor;
drawList.AddFilledRect(layout.rowRects[visibleOffset], rowColor, metrics.cornerRounding);
}
}
void AppendUIEditorTreeViewForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTreeViewLayout& layout,
const std::vector<UIEditorTreeViewItem>& items,
const UIEditorTreeViewPalette& palette,
const UIEditorTreeViewMetrics& metrics) {
drawList.PushClipRect(layout.bounds);
for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) {
const UIEditorTreeViewItem& item = items[layout.visibleItemIndices[visibleOffset]];
if (layout.itemHasChildren[visibleOffset]) {
drawList.AddText(
ResolveDisclosureGlyphPosition(
layout.disclosureRects[visibleOffset],
metrics.labelInsetY),
layout.itemExpanded[visibleOffset] ? "v" : ">",
palette.disclosureColor,
12.0f);
}
drawList.PushClipRect(layout.labelRects[visibleOffset]);
drawList.AddText(
::XCEngine::UI::UIPoint(
layout.labelRects[visibleOffset].x,
layout.labelRects[visibleOffset].y + metrics.labelInsetY),
item.label,
palette.textColor,
12.0f);
drawList.PopClipRect();
}
drawList.PopClipRect();
}
void AppendUIEditorTreeView(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewState& state,
const UIEditorTreeViewPalette& palette,
const UIEditorTreeViewMetrics& metrics) {
const UIEditorTreeViewLayout layout =
BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics);
AppendUIEditorTreeViewBackground(
drawList,
layout,
items,
selectionModel,
state,
palette,
metrics);
AppendUIEditorTreeViewForeground(drawList, layout, items, palette, metrics);
}
} // namespace XCEngine::UI::Editor::Widgets