关键节点

This commit is contained in:
2026-04-25 16:46:01 +08:00
parent 6002d86a7e
commit ef41c44464
516 changed files with 6175 additions and 12401 deletions

View File

@@ -0,0 +1,242 @@
#pragma once
#include <XCEngine/UI/Types.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor::Collections::DragDropInteraction {
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
inline constexpr float kDefaultDragThreshold = 4.0f;
struct State {
std::string armedItemId = {};
std::string draggedItemId = {};
std::string dropTargetItemId = {};
UIPoint pressPosition = {};
bool armed = false;
bool dragging = false;
bool validDropTarget = false;
bool requestPointerCapture = false;
bool requestPointerRelease = false;
};
struct PreviewState {
std::string armedItemId = {};
UIPoint pressPosition = {};
bool armed = false;
bool dragging = false;
};
struct ProcessResult {
bool selectionForced = false;
bool dropCommitted = false;
std::string draggedItemId = {};
std::string dropTargetItemId = {};
};
template <typename StateLike>
inline void ResetTransientRequests(StateLike& state) {
state.requestPointerCapture = false;
state.requestPointerRelease = false;
}
template <typename StateLike>
inline bool HasActivePointerCapture(const StateLike& state) {
return state.dragging;
}
inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) {
const float dx = lhs.x - rhs.x;
const float dy = lhs.y - rhs.y;
return dx * dx + dy * dy;
}
template <typename StateLike>
inline PreviewState BuildPreviewState(const StateLike& state) {
PreviewState preview = {};
preview.armedItemId = state.armedItemId;
preview.pressPosition = state.pressPosition;
preview.armed = state.armed;
preview.dragging = state.dragging;
return preview;
}
template <typename ResolveDraggableItem>
inline bool ProcessPreviewEvent(
PreviewState& preview,
const UIInputEvent& event,
ResolveDraggableItem&& resolveDraggableItem,
float dragThreshold = kDefaultDragThreshold) {
bool suppress = false;
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
preview.armedItemId = resolveDraggableItem(event.position);
preview.pressPosition = event.position;
preview.armed = !preview.armedItemId.empty();
if (!preview.armed) {
preview.armedItemId.clear();
}
}
if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::PointerMove:
if (preview.dragging) {
suppress = true;
break;
}
if (preview.armed &&
ComputeSquaredDistance(event.position, preview.pressPosition) >=
dragThreshold * dragThreshold) {
preview.dragging = !preview.armedItemId.empty();
suppress = preview.dragging;
}
break;
case UIInputEventType::PointerButtonUp:
if (event.pointerButton == UIPointerButton::Left) {
if (preview.dragging) {
suppress = true;
preview.dragging = false;
}
preview.armed = false;
preview.armedItemId.clear();
} else if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::PointerLeave:
if (preview.dragging) {
suppress = true;
}
break;
case UIInputEventType::FocusLost:
preview.armed = false;
preview.dragging = false;
preview.armedItemId.clear();
break;
default:
if (preview.dragging &&
(event.type == UIInputEventType::PointerWheel ||
event.type == UIInputEventType::PointerEnter)) {
suppress = true;
}
break;
}
return suppress;
}
template <typename StateLike, typename Callbacks>
inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) {
state.armed = false;
state.dragging = false;
state.armedItemId.clear();
state.draggedItemId.clear();
callbacks.ResetDropTarget(state);
}
template <typename StateLike, typename Callbacks>
ProcessResult ProcessInputEvents(
StateLike& state,
const std::vector<UIInputEvent>& inputEvents,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold) {
ProcessResult result = {};
for (const UIInputEvent& event : inputEvents) {
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
state.draggedItemId.clear();
callbacks.ResetDropTarget(state);
state.armedItemId = callbacks.ResolveDraggableItem(event.position);
state.pressPosition = event.position;
state.armed = !state.armedItemId.empty();
if (!state.armed) {
state.armedItemId.clear();
}
}
break;
case UIInputEventType::PointerMove:
if (state.armed &&
!state.dragging &&
ComputeSquaredDistance(event.position, state.pressPosition) >=
dragThreshold * dragThreshold) {
state.dragging = !state.armedItemId.empty();
state.draggedItemId = state.armedItemId;
callbacks.ResetDropTarget(state);
if (state.dragging) {
state.requestPointerCapture = true;
if (!callbacks.IsItemSelected(state.draggedItemId)) {
result.selectionForced = callbacks.SelectDraggedItem(state.draggedItemId);
}
}
}
if (state.dragging) {
callbacks.ResetDropTarget(state);
callbacks.UpdateDropTarget(state, state.draggedItemId, event.position);
}
break;
case UIInputEventType::PointerButtonUp:
if (event.pointerButton != UIPointerButton::Left) {
break;
}
if (state.dragging) {
if (state.validDropTarget) {
result.draggedItemId = state.draggedItemId;
result.dropTargetItemId = state.dropTargetItemId;
result.dropCommitted = callbacks.CommitDrop(state, result);
}
ResetInteractionSession(state, callbacks);
state.requestPointerRelease = true;
} else {
state.armed = false;
state.armedItemId.clear();
}
break;
case UIInputEventType::PointerLeave:
if (state.dragging) {
callbacks.ResetDropTarget(state);
}
break;
case UIInputEventType::FocusLost:
{
const bool requestPointerRelease = state.dragging;
ResetInteractionSession(state, callbacks);
state.pressPosition = {};
state.requestPointerCapture = false;
state.requestPointerRelease = requestPointerRelease;
}
break;
default:
break;
}
}
return result;
}
} // namespace XCEngine::UI::Editor::Collections::DragDropInteraction

View File

@@ -0,0 +1,108 @@
#pragma once
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEditor/Fields/UIEditorTextField.h>
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Widgets {
struct UIEditorFilterableTreeHostMetrics {
float horizontalPadding = 6.0f;
float topPadding = 6.0f;
float bottomPadding = 6.0f;
float searchFieldHeight = 24.0f;
float searchTreeGap = 6.0f;
UIEditorTextFieldMetrics searchFieldMetrics = {};
};
struct UIEditorFilterableTreeHostPalette {
UIEditorTextFieldPalette searchFieldPalette = {};
::XCEngine::UI::UIColor placeholderColor =
::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f);
};
struct UIEditorFilterableTreeHostLayout {
::XCEngine::UI::UIRect bounds = {};
::XCEngine::UI::UIRect searchFieldRect = {};
::XCEngine::UI::UIRect treeRect = {};
};
} // namespace XCEngine::UI::Editor::Widgets
namespace XCEngine::UI::Editor {
struct UIEditorFilterableTreeHostState {
UIEditorTextFieldInteractionState searchFieldInteractionState = {};
Widgets::UIEditorTextFieldSpec searchFieldSpec = {};
std::string normalizedQuery = {};
};
struct UIEditorFilterableTreeHostResult {
bool queryChanged = false;
bool filteringActive = false;
bool searchFocused = false;
};
struct UIEditorFilterableTreeHostFrame {
Widgets::UIEditorFilterableTreeHostLayout layout = {};
UIEditorTextFieldInteractionFrame searchFieldFrame = {};
const std::vector<Widgets::UIEditorTreeViewItem>* sourceItems = nullptr;
const ::XCEngine::UI::Widgets::UIExpansionModel* sourceExpansionModel = nullptr;
std::vector<Widgets::UIEditorTreeViewItem> filteredItems = {};
::XCEngine::UI::Widgets::UIExpansionModel filteredExpansionModel = {};
UIEditorFilterableTreeHostResult result = {};
};
inline bool IsUIEditorFilterableTreeHostSearchFocused(
const UIEditorFilterableTreeHostState& state) {
return state.searchFieldInteractionState.textFieldState.focused;
}
inline const std::vector<Widgets::UIEditorTreeViewItem>&
ResolveUIEditorFilterableTreeHostItems(
const UIEditorFilterableTreeHostFrame& frame) {
static const std::vector<Widgets::UIEditorTreeViewItem> kEmptyItems = {};
if (frame.result.filteringActive) {
return frame.filteredItems;
}
return frame.sourceItems != nullptr ? *frame.sourceItems : kEmptyItems;
}
inline const ::XCEngine::UI::Widgets::UIExpansionModel&
ResolveUIEditorFilterableTreeHostExpansionModel(
const UIEditorFilterableTreeHostFrame& frame) {
static const ::XCEngine::UI::Widgets::UIExpansionModel kEmptyExpansionModel = {};
if (frame.result.filteringActive) {
return frame.filteredExpansionModel;
}
return frame.sourceExpansionModel != nullptr
? *frame.sourceExpansionModel
: kEmptyExpansionModel;
}
UIEditorFilterableTreeHostFrame UpdateUIEditorFilterableTreeHost(
UIEditorFilterableTreeHostState& state,
const ::XCEngine::UI::UIRect& bounds,
std::string_view searchFieldId,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const std::vector<Widgets::UIEditorTreeViewItem>& sourceItems,
const ::XCEngine::UI::Widgets::UIExpansionModel& sourceExpansionModel,
const Widgets::UIEditorFilterableTreeHostMetrics& metrics = {});
void AppendUIEditorFilterableTreeHostSearchField(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorFilterableTreeHostFrame& frame,
const UIEditorFilterableTreeHostState& state,
std::string_view placeholderText = "Search",
const Widgets::UIEditorFilterableTreeHostPalette& palette = {},
const Widgets::UIEditorFilterableTreeHostMetrics& metrics = {},
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr);
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,73 @@
#pragma once
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
#include <string_view>
namespace XCEngine::UI::Editor::Collections::GridDragDrop {
using ::XCEngine::UI::UIPoint;
using DragDropInteraction::kDefaultDragThreshold;
using DragDropInteraction::ProcessResult;
using DragDropInteraction::State;
inline void ResetTransientRequests(State& state) {
DragDropInteraction::ResetTransientRequests(state);
}
inline bool HasActivePointerCapture(const State& state) {
return DragDropInteraction::HasActivePointerCapture(state);
}
template <typename Callbacks>
ProcessResult ProcessInputEvents(
State& state,
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold) {
struct AdaptedCallbacks {
Callbacks& callbacks;
std::string ResolveDraggableItem(const UIPoint& point) const {
return callbacks.ResolveDraggableItem(point);
}
void ResetDropTarget(State& state) const {
state.dropTargetItemId.clear();
state.validDropTarget = false;
}
void UpdateDropTarget(
State& state,
std::string_view draggedItemId,
const UIPoint& point) const {
state.dropTargetItemId =
callbacks.ResolveDropTargetItem(draggedItemId, point);
state.validDropTarget =
!state.dropTargetItemId.empty() &&
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
}
bool IsItemSelected(std::string_view itemId) const {
return callbacks.IsItemSelected(itemId);
}
bool SelectDraggedItem(std::string_view itemId) const {
return callbacks.SelectDraggedItem(itemId);
}
bool CommitDrop(State& state, ProcessResult&) const {
return callbacks.CommitDropOnItem(
state.draggedItemId,
state.dropTargetItemId);
}
} adaptedCallbacks{ callbacks };
return DragDropInteraction::ProcessInputEvents(
state,
inputEvents,
adaptedCallbacks,
dragThreshold);
}
} // namespace XCEngine::UI::Editor::Collections::GridDragDrop

View File

@@ -0,0 +1,63 @@
#pragma once
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
#include <XCEngine/UI/Types.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorInlineRenameSessionState {
bool active = false;
std::string itemId = {};
Widgets::UIEditorTextFieldSpec textFieldSpec = {};
UIEditorTextFieldInteractionState textFieldInteraction = {};
};
struct UIEditorInlineRenameSessionRequest {
bool beginSession = false;
std::string itemId = {};
std::string initialText = {};
::XCEngine::UI::UIRect bounds = {};
};
struct UIEditorInlineRenameSessionResult {
bool consumed = false;
bool sessionStarted = false;
bool sessionCommitted = false;
bool sessionCanceled = false;
bool valueChanged = false;
bool active = false;
std::string itemId = {};
std::string valueBefore = {};
std::string valueAfter = {};
UIEditorTextFieldInteractionResult textFieldResult = {};
};
struct UIEditorInlineRenameSessionFrame {
Widgets::UIEditorTextFieldLayout layout = {};
UIEditorInlineRenameSessionResult result = {};
};
Widgets::UIEditorTextFieldMetrics BuildUIEditorInlineRenameTextFieldMetrics(
const ::XCEngine::UI::UIRect& bounds,
const Widgets::UIEditorTextFieldMetrics& metrics = {});
void AppendUIEditorInlineRenameSession(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorInlineRenameSessionFrame& frame,
const UIEditorInlineRenameSessionState& state,
const Widgets::UIEditorTextFieldPalette& palette = {},
const Widgets::UIEditorTextFieldMetrics& metrics = {},
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer = nullptr);
UIEditorInlineRenameSessionFrame UpdateUIEditorInlineRenameSession(
UIEditorInlineRenameSessionState& state,
const UIEditorInlineRenameSessionRequest& request,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorTextFieldMetrics& metrics = {});
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,121 @@
#pragma once
#include <XCEngine/UI/DrawData.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 UIEditorListViewInvalidIndex = static_cast<std::size_t>(-1);
enum class UIEditorListViewHitTargetKind : std::uint8_t {
None = 0,
Row
};
struct UIEditorListViewItem {
std::string itemId = {};
std::string primaryText = {};
std::string secondaryText = {};
float desiredHeight = 0.0f;
};
struct UIEditorListViewState {
std::string hoveredItemId = {};
bool focused = false;
};
struct UIEditorListViewMetrics {
float rowHeight = 44.0f;
float rowGap = 2.0f;
float horizontalPadding = 10.0f;
float primaryTextInsetY = 7.0f;
float secondaryTextInsetY = 23.0f;
float singleLineTextInsetY = 13.0f;
float cornerRounding = 6.0f;
float borderThickness = 1.0f;
float focusedBorderThickness = 2.0f;
};
struct UIEditorListViewPalette {
::XCEngine::UI::UIColor surfaceColor =
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
::XCEngine::UI::UIColor borderColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor focusedBorderColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor rowHoverColor =
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
::XCEngine::UI::UIColor rowSelectedColor =
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f);
::XCEngine::UI::UIColor rowSelectedFocusedColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor primaryTextColor =
::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f);
::XCEngine::UI::UIColor secondaryTextColor =
::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f);
};
struct UIEditorListViewLayout {
::XCEngine::UI::UIRect bounds = {};
std::vector<std::size_t> itemIndices = {};
std::vector<::XCEngine::UI::UIRect> rowRects = {};
std::vector<::XCEngine::UI::UIRect> primaryTextRects = {};
std::vector<::XCEngine::UI::UIRect> secondaryTextRects = {};
std::vector<bool> hasSecondaryText = {};
};
struct UIEditorListViewHitTarget {
UIEditorListViewHitTargetKind kind = UIEditorListViewHitTargetKind::None;
std::size_t visibleIndex = UIEditorListViewInvalidIndex;
std::size_t itemIndex = UIEditorListViewInvalidIndex;
};
bool IsUIEditorListViewPointInside(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point);
std::size_t FindUIEditorListViewItemIndex(
const std::vector<UIEditorListViewItem>& items,
std::string_view itemId);
UIEditorListViewLayout BuildUIEditorListViewLayout(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorListViewItem>& items,
const UIEditorListViewMetrics& metrics = {});
UIEditorListViewHitTarget HitTestUIEditorListView(
const UIEditorListViewLayout& layout,
const ::XCEngine::UI::UIPoint& point);
void AppendUIEditorListViewBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorListViewLayout& layout,
const std::vector<UIEditorListViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const UIEditorListViewState& state,
const UIEditorListViewPalette& palette = {},
const UIEditorListViewMetrics& metrics = {});
void AppendUIEditorListViewForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorListViewLayout& layout,
const std::vector<UIEditorListViewItem>& items,
const UIEditorListViewPalette& palette = {},
const UIEditorListViewMetrics& metrics = {});
void AppendUIEditorListView(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorListViewItem>& items,
const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const UIEditorListViewState& state,
const UIEditorListViewPalette& palette = {},
const UIEditorListViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor::Widgets

View File

@@ -0,0 +1,47 @@
#pragma once
#include <XCEditor/Collections/UIEditorListView.h>
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorListViewInteractionState {
Widgets::UIEditorListViewState listViewState = {};
::XCEngine::UI::Widgets::UIKeyboardNavigationModel keyboardNavigation = {};
std::string selectionAnchorId = {};
::XCEngine::UI::UIPoint pointerPosition = {};
bool hasPointerPosition = false;
};
struct UIEditorListViewInteractionResult {
bool consumed = false;
bool selectionChanged = false;
bool keyboardNavigated = false;
bool secondaryClicked = false;
bool renameRequested = false;
Widgets::UIEditorListViewHitTarget hitTarget = {};
std::string selectedItemId = {};
std::string renameItemId = {};
std::size_t selectedIndex = Widgets::UIEditorListViewInvalidIndex;
};
struct UIEditorListViewInteractionFrame {
Widgets::UIEditorListViewLayout layout = {};
UIEditorListViewInteractionResult result = {};
};
UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction(
UIEditorListViewInteractionState& state,
::XCEngine::UI::Widgets::UISelectionModel& selectionModel,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<Widgets::UIEditorListViewItem>& items,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorListViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,96 @@
#pragma once
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Types.h>
#include <cstdint>
namespace XCEngine::UI::Editor::Widgets {
enum class UIEditorScrollViewHitTargetKind : std::uint8_t {
None = 0,
Content,
ScrollbarTrack,
ScrollbarThumb
};
struct UIEditorScrollViewState {
bool focused = false;
bool hovered = false;
bool scrollbarHovered = false;
bool draggingScrollbarThumb = false;
};
struct UIEditorScrollViewMetrics {
float scrollbarWidth = 10.0f;
float scrollbarInset = 4.0f;
float minThumbHeight = 28.0f;
float cornerRounding = 8.0f;
float borderThickness = 1.0f;
float focusedBorderThickness = 2.0f;
float wheelStep = 48.0f;
};
struct UIEditorScrollViewPalette {
::XCEngine::UI::UIColor surfaceColor =
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
::XCEngine::UI::UIColor borderColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor focusedBorderColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor scrollbarTrackColor =
::XCEngine::UI::UIColor(0.09f, 0.09f, 0.09f, 1.0f);
::XCEngine::UI::UIColor scrollbarThumbColor =
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
::XCEngine::UI::UIColor scrollbarThumbHoverColor =
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f);
::XCEngine::UI::UIColor scrollbarThumbActiveColor =
::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f);
};
struct UIEditorScrollViewLayout {
::XCEngine::UI::UIRect bounds = {};
::XCEngine::UI::UIRect contentRect = {};
::XCEngine::UI::UIRect scrollbarTrackRect = {};
::XCEngine::UI::UIRect scrollbarThumbRect = {};
float contentHeight = 0.0f;
float verticalOffset = 0.0f;
float maxOffset = 0.0f;
bool hasScrollbar = false;
};
struct UIEditorScrollViewHitTarget {
UIEditorScrollViewHitTargetKind kind = UIEditorScrollViewHitTargetKind::None;
};
bool IsUIEditorScrollViewPointInside(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point);
float ClampUIEditorScrollViewOffset(
const ::XCEngine::UI::UIRect& bounds,
float contentHeight,
float verticalOffset,
const UIEditorScrollViewMetrics& metrics = {});
UIEditorScrollViewLayout BuildUIEditorScrollViewLayout(
const ::XCEngine::UI::UIRect& bounds,
float contentHeight,
float verticalOffset,
const UIEditorScrollViewMetrics& metrics = {});
::XCEngine::UI::UIPoint ResolveUIEditorScrollViewContentOrigin(
const UIEditorScrollViewLayout& layout);
UIEditorScrollViewHitTarget HitTestUIEditorScrollView(
const UIEditorScrollViewLayout& layout,
const ::XCEngine::UI::UIPoint& point);
void AppendUIEditorScrollViewBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorScrollViewLayout& layout,
const UIEditorScrollViewState& state,
const UIEditorScrollViewPalette& palette = {},
const UIEditorScrollViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor::Widgets

View File

@@ -0,0 +1,47 @@
#pragma once
#include <XCEditor/Collections/UIEditorScrollView.h>
#include <XCEngine/UI/Types.h>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorScrollViewInteractionState {
Widgets::UIEditorScrollViewState scrollViewState = {};
::XCEngine::UI::UIPoint pointerPosition = {};
float thumbDragStartPointerY = 0.0f;
float thumbDragStartOffset = 0.0f;
bool hasPointerPosition = false;
};
struct UIEditorScrollViewInteractionResult {
bool consumed = false;
bool offsetChanged = false;
bool focusChanged = false;
bool startedThumbDrag = false;
bool endedThumbDrag = false;
Widgets::UIEditorScrollViewHitTarget hitTarget = {};
float verticalOffset = 0.0f;
};
struct UIEditorScrollViewInteractionFrame {
Widgets::UIEditorScrollViewLayout layout = {};
UIEditorScrollViewInteractionResult result = {};
};
inline bool HasActiveUIEditorScrollViewPointerCapture(
const UIEditorScrollViewInteractionState& state) {
return state.scrollViewState.draggingScrollbarThumb;
}
UIEditorScrollViewInteractionFrame UpdateUIEditorScrollViewInteraction(
UIEditorScrollViewInteractionState& state,
float& verticalOffset,
const ::XCEngine::UI::UIRect& bounds,
float contentHeight,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorScrollViewMetrics& metrics = {});
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,135 @@
#pragma once
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Layout/UITabStripLayout.h>
#include <XCEngine/UI/Widgets/UITabStripModel.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Widgets {
inline constexpr std::size_t UIEditorTabStripInvalidIndex =
::XCEngine::UI::Widgets::UITabStripModel::InvalidIndex;
struct UIEditorTabStripItem {
std::string tabId = {};
std::string title = {};
// Pure glyph advance for the rendered title. Header padding is added by layout.
float measuredLabelWidth = 0.0f;
};
struct UIEditorTabStripState {
std::size_t selectedIndex = UIEditorTabStripInvalidIndex;
std::size_t hoveredIndex = UIEditorTabStripInvalidIndex;
bool focused = false;
};
struct UIEditorTabStripMetrics {
::XCEngine::UI::Layout::UITabStripMetrics layoutMetrics =
::XCEngine::UI::Layout::UITabStripMetrics{ 22.0f, 68.0f, 8.0f, 1.0f };
float estimatedGlyphWidth = 6.0f;
float labelInsetX = 8.0f;
float labelInsetY = -0.5f;
float baseBorderThickness = 1.0f;
float selectedBorderThickness = 1.0f;
float dragThreshold = 6.0f;
};
struct UIEditorTabStripPalette {
::XCEngine::UI::UIColor stripBackgroundColor =
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
::XCEngine::UI::UIColor headerBackgroundColor =
::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f);
::XCEngine::UI::UIColor contentBackgroundColor =
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
::XCEngine::UI::UIColor headerContentSeparatorColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor tabColor =
::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f);
::XCEngine::UI::UIColor tabHoveredColor =
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
::XCEngine::UI::UIColor tabSelectedColor =
::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f);
::XCEngine::UI::UIColor tabBorderColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor tabHoveredBorderColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor tabSelectedBorderColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor textPrimary =
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
::XCEngine::UI::UIColor textSecondary =
::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f);
};
struct UIEditorTabStripLayout {
::XCEngine::UI::UIRect bounds = {};
::XCEngine::UI::UIRect headerRect = {};
::XCEngine::UI::UIRect contentRect = {};
std::vector<::XCEngine::UI::UIRect> tabHeaderRects = {};
std::size_t selectedIndex = UIEditorTabStripInvalidIndex;
};
enum class UIEditorTabStripHitTargetKind : std::uint8_t {
None = 0,
HeaderBackground,
Tab,
Content
};
struct UIEditorTabStripHitTarget {
UIEditorTabStripHitTargetKind kind = UIEditorTabStripHitTargetKind::None;
std::size_t index = UIEditorTabStripInvalidIndex;
};
float ResolveUIEditorTabStripMeasuredLabelWidth(
const UIEditorTabStripItem& item,
const UIEditorTabStripMetrics& metrics = {},
const UIEditorTextMeasurer* textMeasurer = nullptr);
std::size_t ResolveUIEditorTabStripSelectedIndex(
const std::vector<UIEditorTabStripItem>& items,
std::string_view selectedTabId,
std::size_t fallbackIndex = UIEditorTabStripInvalidIndex);
UIEditorTabStripLayout BuildUIEditorTabStripLayout(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTabStripItem>& items,
const UIEditorTabStripState& state,
const UIEditorTabStripMetrics& metrics = {});
UIEditorTabStripHitTarget HitTestUIEditorTabStrip(
const UIEditorTabStripLayout& layout,
const UIEditorTabStripState& state,
const ::XCEngine::UI::UIPoint& point);
void AppendUIEditorTabStripBackground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTabStripLayout& layout,
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette = {},
const UIEditorTabStripMetrics& metrics = {});
void AppendUIEditorTabStripForeground(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorTabStripLayout& layout,
const std::vector<UIEditorTabStripItem>& items,
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette = {},
const UIEditorTabStripMetrics& metrics = {});
void AppendUIEditorTabStrip(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTabStripItem>& items,
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette = {},
const UIEditorTabStripMetrics& metrics = {});
} // namespace XCEngine::UI::Editor::Widgets

View File

@@ -0,0 +1,60 @@
#pragma once
#include <XCEditor/Collections/UIEditorTabStrip.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Widgets/UIDragDropInteraction.h>
#include <XCEngine/UI/Widgets/UITabStripModel.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorTabStripInteractionState {
Widgets::UIEditorTabStripState tabStripState = {};
::XCEngine::UI::Widgets::UITabStripModel navigationModel = {};
::XCEngine::UI::Widgets::UIDragDropState dragState = {};
Widgets::UIEditorTabStripHitTarget pressedTarget = {};
::XCEngine::UI::UIPoint pointerPosition = {};
std::size_t dragSourceIndex = Widgets::UIEditorTabStripInvalidIndex;
bool hasPointerPosition = false;
bool dragCaptureActive = false;
};
struct UIEditorTabStripInteractionResult {
bool consumed = false;
bool selectionChanged = false;
bool keyboardNavigated = false;
bool requestPointerCapture = false;
bool releasePointerCapture = false;
bool dragStarted = false;
bool dragEnded = false;
bool dragCanceled = false;
Widgets::UIEditorTabStripHitTarget hitTarget = {};
std::string selectedTabId = {};
std::size_t selectedIndex = Widgets::UIEditorTabStripInvalidIndex;
std::size_t closedIndex = Widgets::UIEditorTabStripInvalidIndex;
std::string draggedTabId = {};
std::size_t dragSourceIndex = Widgets::UIEditorTabStripInvalidIndex;
};
struct UIEditorTabStripInteractionFrame {
Widgets::UIEditorTabStripLayout layout = {};
UIEditorTabStripInteractionResult result = {};
bool focused = false;
};
// Clears transient pointer-driven state while preserving selection/navigation.
void ClearUIEditorTabStripTransientInteraction(
UIEditorTabStripInteractionState& state);
UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction(
UIEditorTabStripInteractionState& state,
std::string& selectedTabId,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<Widgets::UIEditorTabStripItem>& items,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorTabStripMetrics& metrics = {});
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,398 @@
#pragma once
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEngine/UI/Types.h>
#include <algorithm>
#include <concepts>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Collections::TreeDragDrop {
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
using DragDropInteraction::kDefaultDragThreshold;
using Widgets::HitTestUIEditorTreeView;
using Widgets::UIEditorTreeViewHitTarget;
using Widgets::UIEditorTreeViewHitTargetKind;
using Widgets::UIEditorTreeViewInvalidIndex;
using ::XCEngine::UI::Editor::UIEditorTreeViewDropPreviewPlacement;
struct State : DragDropInteraction::State {
bool dropToRoot = false;
UIEditorTreeViewDropPreviewPlacement dropPlacement =
UIEditorTreeViewDropPreviewPlacement::None;
};
struct ProcessResult : DragDropInteraction::ProcessResult {
bool droppedToRoot = false;
};
inline void ResetTransientRequests(State& state) {
DragDropInteraction::ResetTransientRequests(state);
}
inline bool HasActivePointerCapture(const State& state) {
return DragDropInteraction::HasActivePointerCapture(state);
}
inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
template <typename Callbacks>
inline constexpr bool SupportsSiblingInsertion() {
return requires(
Callbacks& callbacks,
std::string_view draggedItemId,
std::string_view targetItemId) {
{ callbacks.CanInsertBeforeItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
{ callbacks.CanInsertAfterItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
{ callbacks.CommitInsertBeforeItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
{ callbacks.CommitInsertAfterItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
};
}
inline UIEditorTreeViewDropPreviewPlacement ResolveRowDropPlacement(
const Widgets::UIEditorTreeViewLayout& layout,
const UIEditorTreeViewHitTarget& hitTarget,
const UIPoint& point) {
if (hitTarget.visibleIndex >= layout.rowRects.size()) {
return UIEditorTreeViewDropPreviewPlacement::None;
}
const UIRect& rowRect = layout.rowRects[hitTarget.visibleIndex];
const float edgeBand =
(std::min)((std::max)(rowRect.height * 0.25f, 4.0f), 8.0f);
if (point.y <= rowRect.y + edgeBand) {
return UIEditorTreeViewDropPreviewPlacement::BeforeItem;
}
if (point.y >= rowRect.y + rowRect.height - edgeBand) {
return UIEditorTreeViewDropPreviewPlacement::AfterItem;
}
return UIEditorTreeViewDropPreviewPlacement::OnItem;
}
inline const Widgets::UIEditorTreeViewItem* ResolveGapInsertionItem(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const UIPoint& point,
UIEditorTreeViewDropPreviewPlacement& outPlacement) {
outPlacement = UIEditorTreeViewDropPreviewPlacement::None;
if (layout.rowRects.empty()) {
return nullptr;
}
if (point.y < layout.rowRects.front().y) {
outPlacement = UIEditorTreeViewDropPreviewPlacement::BeforeItem;
const std::size_t itemIndex = layout.visibleItemIndices.front();
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
}
for (std::size_t visibleIndex = 0u; visibleIndex + 1u < layout.rowRects.size(); ++visibleIndex) {
const UIRect& currentRow = layout.rowRects[visibleIndex];
const UIRect& nextRow = layout.rowRects[visibleIndex + 1u];
if (point.y > currentRow.y + currentRow.height &&
point.y < nextRow.y) {
outPlacement = UIEditorTreeViewDropPreviewPlacement::BeforeItem;
const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex + 1u];
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
}
}
const UIRect& lastRow = layout.rowRects.back();
if (point.y > lastRow.y + lastRow.height) {
outPlacement = UIEditorTreeViewDropPreviewPlacement::AfterItem;
const std::size_t itemIndex = layout.visibleItemIndices.back();
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
}
return nullptr;
}
inline const Widgets::UIEditorTreeViewItem* ResolveHitItem(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const UIPoint& point,
UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) {
const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point);
if (hitTargetOutput != nullptr) {
*hitTargetOutput = hitTarget;
}
if (hitTarget.itemIndex >= items.size()) {
return nullptr;
}
return &items[hitTarget.itemIndex];
}
inline std::size_t FindVisibleIndexForItemId(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
std::string_view itemId) {
for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) {
const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex];
if (itemIndex < items.size() && items[itemIndex].itemId == itemId) {
return visibleIndex;
}
}
return UIEditorTreeViewInvalidIndex;
}
inline bool IsSuppressedPointerInputEvent(
const DragDropInteraction::UIInputEvent& event) {
switch (event.type) {
case ::XCEngine::UI::UIInputEventType::PointerMove:
case ::XCEngine::UI::UIInputEventType::PointerButtonDown:
case ::XCEngine::UI::UIInputEventType::PointerButtonUp:
case ::XCEngine::UI::UIInputEventType::PointerWheel:
case ::XCEngine::UI::UIInputEventType::PointerEnter:
return true;
default:
return false;
}
}
inline std::vector<DragDropInteraction::UIInputEvent> FilterPointerInputEvents(
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
bool suppressPointerInput) {
if (!suppressPointerInput) {
return inputEvents;
}
std::vector<DragDropInteraction::UIInputEvent> filteredEvents = {};
filteredEvents.reserve(inputEvents.size());
for (const DragDropInteraction::UIInputEvent& event : inputEvents) {
if (!IsSuppressedPointerInputEvent(event)) {
filteredEvents.push_back(event);
}
}
return filteredEvents;
}
inline std::vector<DragDropInteraction::UIInputEvent> BuildInteractionInputEvents(
const State& state,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<DragDropInteraction::UIInputEvent>& rawEvents,
float dragThreshold = kDefaultDragThreshold,
bool suppressPointerInput = false) {
auto resolveDraggableItem =
[&layout, &items](const UIPoint& point) -> std::string {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
return hitItem->itemId;
}
return {};
};
DragDropInteraction::PreviewState preview =
DragDropInteraction::BuildPreviewState(state);
const std::vector<DragDropInteraction::UIInputEvent> inputEvents =
FilterPointerInputEvents(rawEvents, suppressPointerInput);
std::vector<DragDropInteraction::UIInputEvent> filteredEvents = {};
filteredEvents.reserve(inputEvents.size());
for (const DragDropInteraction::UIInputEvent& event : inputEvents) {
if (!DragDropInteraction::ProcessPreviewEvent(
preview,
event,
resolveDraggableItem,
dragThreshold)) {
filteredEvents.push_back(event);
}
}
return filteredEvents;
}
template <typename Callbacks>
ProcessResult ProcessInputEvents(
State& state,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
const UIRect& bounds,
Callbacks& callbacks,
float dragThreshold = kDefaultDragThreshold,
bool suppressPointerInput = false) {
struct AdaptedCallbacks {
const Widgets::UIEditorTreeViewLayout& layout;
const std::vector<Widgets::UIEditorTreeViewItem>& items;
const UIRect& bounds;
Callbacks& callbacks;
bool droppedToRoot = false;
std::string ResolveDraggableItem(const UIPoint& point) const {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
return hitItem->itemId;
}
return {};
}
void ResetDropTarget(State& state) const {
state.dropTargetItemId.clear();
state.dropToRoot = false;
state.dropPlacement = UIEditorTreeViewDropPreviewPlacement::None;
state.validDropTarget = false;
}
void UpdateDropTarget(
State& state,
std::string_view draggedItemId,
const UIPoint& point) const {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, items, point, &hitTarget);
if (hitItem != nullptr &&
(hitTarget.kind == UIEditorTreeViewHitTargetKind::Row ||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) {
UIEditorTreeViewDropPreviewPlacement placement =
UIEditorTreeViewDropPreviewPlacement::OnItem;
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
placement =
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure
? UIEditorTreeViewDropPreviewPlacement::OnItem
: ResolveRowDropPlacement(layout, hitTarget, point);
}
state.dropTargetItemId = hitItem->itemId;
state.dropPlacement = placement;
switch (placement) {
case UIEditorTreeViewDropPreviewPlacement::BeforeItem:
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
state.validDropTarget =
callbacks.CanInsertBeforeItem(
draggedItemId,
state.dropTargetItemId);
}
break;
case UIEditorTreeViewDropPreviewPlacement::AfterItem:
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
state.validDropTarget =
callbacks.CanInsertAfterItem(
draggedItemId,
state.dropTargetItemId);
}
break;
case UIEditorTreeViewDropPreviewPlacement::OnItem:
state.validDropTarget =
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
break;
default:
break;
}
return;
}
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
UIEditorTreeViewDropPreviewPlacement gapPlacement =
UIEditorTreeViewDropPreviewPlacement::None;
const Widgets::UIEditorTreeViewItem* gapItem =
ResolveGapInsertionItem(layout, items, point, gapPlacement);
if (gapItem != nullptr &&
gapPlacement != UIEditorTreeViewDropPreviewPlacement::None) {
state.dropTargetItemId = gapItem->itemId;
state.dropPlacement = gapPlacement;
state.validDropTarget =
gapPlacement == UIEditorTreeViewDropPreviewPlacement::BeforeItem
? callbacks.CanInsertBeforeItem(
draggedItemId,
state.dropTargetItemId)
: callbacks.CanInsertAfterItem(
draggedItemId,
state.dropTargetItemId);
return;
}
}
if (ContainsPoint(bounds, point)) {
state.dropToRoot = true;
state.dropPlacement = UIEditorTreeViewDropPreviewPlacement::Root;
state.validDropTarget = callbacks.CanDropToRoot(draggedItemId);
}
}
bool IsItemSelected(std::string_view itemId) const {
return callbacks.IsItemSelected(itemId);
}
bool SelectDraggedItem(std::string_view itemId) const {
return callbacks.SelectDraggedItem(itemId);
}
bool CommitDrop(State& state, DragDropInteraction::ProcessResult&) {
droppedToRoot = state.dropToRoot;
switch (state.dropPlacement) {
case UIEditorTreeViewDropPreviewPlacement::BeforeItem:
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
return callbacks.CommitInsertBeforeItem(
state.draggedItemId,
state.dropTargetItemId);
}
return false;
case UIEditorTreeViewDropPreviewPlacement::AfterItem:
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
return callbacks.CommitInsertAfterItem(
state.draggedItemId,
state.dropTargetItemId);
}
return false;
case UIEditorTreeViewDropPreviewPlacement::Root:
return callbacks.CommitDropToRoot(state.draggedItemId);
case UIEditorTreeViewDropPreviewPlacement::OnItem:
return callbacks.CommitDropOnItem(
state.draggedItemId,
state.dropTargetItemId);
case UIEditorTreeViewDropPreviewPlacement::None:
default:
return false;
}
}
} adaptedCallbacks{ layout, items, bounds, callbacks };
ProcessResult result = {};
const std::vector<DragDropInteraction::UIInputEvent> filteredInputEvents =
FilterPointerInputEvents(inputEvents, suppressPointerInput);
const DragDropInteraction::ProcessResult interactionResult =
DragDropInteraction::ProcessInputEvents(
state,
filteredInputEvents,
adaptedCallbacks,
dragThreshold);
result.selectionForced = interactionResult.selectionForced;
result.dropCommitted = interactionResult.dropCommitted;
result.draggedItemId = interactionResult.draggedItemId;
result.dropTargetItemId = interactionResult.dropTargetItemId;
result.droppedToRoot = adaptedCallbacks.droppedToRoot;
return result;
}
} // namespace XCEngine::UI::Editor::Collections::TreeDragDrop

View File

@@ -0,0 +1,233 @@
#pragma once
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
#include <XCEditor/Collections/UIEditorScrollView.h>
#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;
::XCEngine::UI::UITextureHandle leadingIcon = {};
};
struct UIEditorTreeViewState {
std::string hoveredItemId = {};
bool focused = false;
UIEditorScrollViewState scrollViewState = {};
};
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 iconExtent = 18.0f;
float iconLabelGap = 2.0f;
float iconInsetY = 0.0f;
float labelInsetY = 6.0f;
float cornerRounding = 6.0f;
float borderThickness = 1.0f;
float focusedBorderThickness = 2.0f;
UIEditorScrollViewMetrics scrollViewMetrics = {};
};
struct UIEditorTreeViewPalette {
::XCEngine::UI::UIColor surfaceColor =
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
::XCEngine::UI::UIColor borderColor =
::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
::XCEngine::UI::UIColor focusedBorderColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor rowHoverColor =
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
::XCEngine::UI::UIColor rowSelectedColor =
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f);
::XCEngine::UI::UIColor rowSelectedFocusedColor =
::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f);
::XCEngine::UI::UIColor disclosureColor =
::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f);
::XCEngine::UI::UIColor textColor =
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
UIEditorScrollViewPalette scrollViewPalette = {};
};
struct UIEditorTreeViewLayout {
::XCEngine::UI::UIRect bounds = {};
UIEditorScrollViewLayout scrollViewLayout = {};
std::vector<std::size_t> visibleItemIndices = {};
std::vector<::XCEngine::UI::UIRect> rowRects = {};
std::vector<::XCEngine::UI::UIRect> disclosureRects = {};
std::vector<::XCEngine::UI::UIRect> iconRects = {};
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);
float MeasureUIEditorTreeViewContentHeight(
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewMetrics& metrics = {});
UIEditorTreeViewLayout BuildUIEditorTreeViewLayout(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<UIEditorTreeViewItem>& items,
const ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel,
const UIEditorTreeViewMetrics& metrics = {},
float verticalOffset = 0.0f);
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
namespace XCEngine::UI::Editor {
enum class UIEditorTreeViewDropPreviewPlacement : std::uint8_t {
None = 0,
OnItem,
BeforeItem,
AfterItem,
Root
};
bool HasUIEditorTreeViewValidBounds(
const ::XCEngine::UI::UIRect& bounds);
std::size_t FindUIEditorTreeViewVisibleItemIndex(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
std::string_view itemId);
::XCEngine::UI::UIRect BuildUIEditorTreeViewInlineRenameBounds(
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
std::string_view itemId,
const Widgets::UIEditorTextFieldMetrics& hostedMetrics,
float minWidth = 120.0f,
float trailingPadding = 8.0f);
Widgets::UIEditorTextFieldMetrics ResolveUIEditorTreeViewHostedTextFieldMetrics();
Widgets::UIEditorTextFieldMetrics BuildUIEditorTreeViewInlineRenameMetrics(
const ::XCEngine::UI::UIRect& bounds);
Widgets::UIEditorTextFieldPalette ResolveUIEditorTreeViewInlineRenamePalette();
bool TryStartUIEditorTreeViewInlineRenameSession(
UIEditorInlineRenameSessionState& renameState,
UIEditorInlineRenameSessionFrame& renameFrame,
std::string_view itemId,
std::string initialText,
const ::XCEngine::UI::UIRect& bounds);
void UpdateUIEditorTreeViewInlineRenameSession(
UIEditorInlineRenameSessionState& renameState,
UIEditorInlineRenameSessionFrame& renameFrame,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents);
void AppendUIEditorTreeViewDropPreview(
::XCEngine::UI::UIDrawList& drawList,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
bool active,
UIEditorTreeViewDropPreviewPlacement placement,
std::string_view dropTargetItemId,
const ::XCEngine::UI::UIColor& previewColor,
float borderThickness = 1.0f,
float cornerRounding = 0.0f);
void AppendUIEditorTreeViewDropPreview(
::XCEngine::UI::UIDrawList& drawList,
const Widgets::UIEditorTreeViewLayout& layout,
const std::vector<Widgets::UIEditorTreeViewItem>& items,
bool active,
bool dropToRoot,
std::string_view dropTargetItemId,
const ::XCEngine::UI::UIColor& previewColor,
float borderThickness = 1.0f,
float cornerRounding = 0.0f);
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,72 @@
#pragma once
#include <XCEditor/Collections/UIEditorScrollViewInteraction.h>
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEngine/UI/Types.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UIKeyboardNavigationModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <string>
#include <vector>
namespace XCEngine::UI::Editor {
struct UIEditorHostedTreeViewInputOptions {
bool allowInteraction = false;
bool hasInputFocus = false;
bool captureActive = false;
};
struct UIEditorTreeViewInteractionState {
Widgets::UIEditorTreeViewState treeViewState = {};
UIEditorScrollViewInteractionState scrollViewInteractionState = {};
::XCEngine::UI::Widgets::UIKeyboardNavigationModel keyboardNavigation = {};
std::string selectionAnchorId = {};
::XCEngine::UI::UIPoint pointerPosition = {};
float verticalOffset = 0.0f;
bool hasPointerPosition = false;
};
struct UIEditorTreeViewInteractionResult {
bool consumed = false;
bool selectionChanged = false;
bool expansionChanged = false;
bool keyboardNavigated = false;
bool secondaryClicked = false;
bool renameRequested = false;
Widgets::UIEditorTreeViewHitTarget hitTarget = {};
std::string selectedItemId = {};
std::string renameItemId = {};
std::string toggledItemId = {};
std::size_t selectedVisibleIndex = Widgets::UIEditorTreeViewInvalidIndex;
};
struct UIEditorTreeViewInteractionFrame {
Widgets::UIEditorTreeViewLayout layout = {};
UIEditorTreeViewInteractionResult result = {};
};
inline bool HasActiveUIEditorTreeViewPointerCapture(
const UIEditorTreeViewInteractionState& state) {
return HasActiveUIEditorScrollViewPointerCapture(state.scrollViewInteractionState);
}
std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorHostedTreeViewInputEvents(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const UIEditorHostedTreeViewInputOptions& options,
bool synthesizeFocusGained = false,
bool synthesizeFocusLost = false);
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