关键节点
This commit is contained in:
@@ -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
|
||||
108
editor/include/XCEditor/Collections/UIEditorFilterableTreeHost.h
Normal file
108
editor/include/XCEditor/Collections/UIEditorFilterableTreeHost.h
Normal 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
|
||||
73
editor/include/XCEditor/Collections/UIEditorGridDragDrop.h
Normal file
73
editor/include/XCEditor/Collections/UIEditorGridDragDrop.h
Normal 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
|
||||
@@ -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
|
||||
121
editor/include/XCEditor/Collections/UIEditorListView.h
Normal file
121
editor/include/XCEditor/Collections/UIEditorListView.h
Normal 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
|
||||
@@ -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
|
||||
96
editor/include/XCEditor/Collections/UIEditorScrollView.h
Normal file
96
editor/include/XCEditor/Collections/UIEditorScrollView.h
Normal 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
|
||||
@@ -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
|
||||
135
editor/include/XCEditor/Collections/UIEditorTabStrip.h
Normal file
135
editor/include/XCEditor/Collections/UIEditorTabStrip.h
Normal 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
|
||||
@@ -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
|
||||
398
editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h
Normal file
398
editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h
Normal 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
|
||||
233
editor/include/XCEditor/Collections/UIEditorTreeView.h
Normal file
233
editor/include/XCEditor/Collections/UIEditorTreeView.h
Normal 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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user