-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScrollComponent.h
More file actions
228 lines (178 loc) · 6.36 KB
/
Copy pathScrollComponent.h
File metadata and controls
228 lines (178 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#pragma once
#include <stdint.h>
#include <functional>
#include <vector>
#include "DekiBehaviour.h"
#include "DekiMath.h"
#include "assets/AssetRef.h"
#include "reflection/ObjectRef.h"
#include "reflection/DekiProperty.h"
// Forward declarations
class DekiObject;
class ClipComponent;
class ScrollElement;
class Scene;
class InputCollider;
/**
* @brief Scroll direction enum
*/
enum class ScrollDirection : uint8_t
{
Vertical = 0,
Horizontal = 1
};
/**
* @brief Scroll mode enum
*/
enum class ScrollMode : uint8_t
{
NonTemplate = 0, // User manages all children directly
Template = 1 // Template child cloned into pooled slots with bind callback
};
/**
* @brief Callback for binding item data to a slot object in Template mode
*/
using ScrollItemCallback = std::function<void(DekiObject* slot, int32_t itemIndex)>;
/**
* @brief Optimized scroll component with child object pooling/reuse
*
* Supports two modes:
* - NonTemplate: User pre-creates all item children. ScrollComponent manages
* their visibility and positioning based on scroll offset.
* - Template: User designs a "Template" child. ScrollComponent clones it
* to create a pool of reusable slots. A bind callback fires only when
* a slot's assigned item index changes (reuse optimization).
*
* Features:
* - Virtual scrolling (only visible items are active)
* - Momentum-based scrolling with inertia
* - Edge bounce physics
* - Fixed-point Q16.16 math for sub-pixel precision
* - Touch/drag input support
* - Vertical and horizontal scrolling
*/
class ScrollComponent : public DekiBehaviour
{
DEKI_COMPONENT(ScrollComponent, DekiBehaviour, "2D", "2d1b84cb-8881-4457-8361-98dec7a84498", "DEKI_FEATURE_SCROLL")
DEKI_DESCRIPTION("Scrolls its children by dragging, with momentum.")
using DekiBehaviour::Update;
public:
// === Editor-visible properties ===
// InputCollider reference (required for receiving input)
DEKI_EXPORT
ObjectRef<InputCollider> input_collider;
DEKI_EXPORT
ScrollMode mode = ScrollMode::NonTemplate;
DEKI_VISIBLE_WHEN(mode, Template)
DEKI_EXPORT
Deki::AssetRef<Scene> item_scene;
DEKI_EXPORT
ScrollDirection direction = ScrollDirection::Vertical;
DEKI_EXPORT
DEKI_UNIT(Distance)
float item_spacing;
DEKI_EXPORT
DEKI_UNIT(Distance)
float padding_top;
DEKI_EXPORT
DEKI_UNIT(Distance)
float padding_bottom;
DEKI_EXPORT
DEKI_UNIT(Distance)
float padding_left;
DEKI_EXPORT
DEKI_UNIT(Distance)
float padding_right;
DEKI_EXPORT
float deceleration;
DEKI_EXPORT
float bounce_stiffness;
DEKI_EXPORT
bool enable_bounce;
DEKI_EXPORT
bool enable_inertia;
DEKI_EXPORT
bool reverse_drag;
// === Construction ===
ScrollComponent();
virtual ~ScrollComponent();
// === Public API (world-space values use float) ===
float GetViewportWidth() const;
float GetViewportHeight() const;
void SetItemSpacing(float spacing);
void SetItemCount(int32_t count);
void SetDirection(ScrollDirection dir);
void SetOnBindItem(const ScrollItemCallback& callback);
void ScrollTo(float position, bool smooth = true);
void ScrollToItem(int32_t index, bool smooth = true);
float GetScrollPosition() const;
int32_t GetFirstVisibleIndex() const { return m_FirstVisibleIndex; }
int32_t GetLastVisibleIndex() const { return m_LastVisibleIndex; }
int32_t GetSlotCount() const;
DekiObject* GetSlotObject(int32_t slotIndex) const;
int32_t GetSlotItemIndex(int32_t slotIndex) const;
// === Lifecycle ===
void Start() override;
bool NeedsRuntimeUpdate() const override;
void RuntimeUpdate(float deltaTime) override;
void OnPropertyChanged(const char* propertyName) override;
// === Child management (public for editor) ===
void EnsureChildObjects(DekiObject* owner);
void SyncChildObjects(DekiObject* owner);
// === Physics ===
void Update(float delta_time);
private:
// All sizes/offsets/positions below are world meters.
// Child object references
DekiObject* m_ClipObj = nullptr;
DekiObject* m_TemplateObj = nullptr; // Template mode: blueprint child (hidden)
std::vector<DekiObject*> m_SlotObjs; // Template mode: pool slots
std::vector<int32_t> m_SlotItemIndices; // Template mode: item index per slot (-1 = unassigned)
std::vector<DekiObject*> m_NonTemplateChildren; // NonTemplate mode: all item children
std::vector<float> m_NonTemplateSizes; // NonTemplate mode: per-child size in scroll direction (m)
std::vector<float> m_NonTemplateOffsets; // NonTemplate mode: prefix-sum offsets, size = itemCount + 1 (m)
// Computed from ScrollElement
float m_ItemSize = 0.0f;
int32_t m_ItemCount = 0;
// Scroll state (meters)
float m_ScrollOffset = 0.0f;
float m_ScrollVelocity = 0.0f;
// Smooth scroll animation
float m_ScrollTarget = 0.0f;
bool m_IsSmoothScrolling = false;
// Touch tracking
bool m_IsDragging = false;
bool m_DragConfirmed = false;
// Drag threshold: ~10 px at ppm=16 (preserves prior tap/drag feel).
float m_DragThreshold;
float m_LastTouchPos = 0.0f;
float m_TouchStartPos = 0.0f;
float m_TouchStartOffset = 0.0f;
// Velocity ring buffer for smooth momentum
static constexpr int kVelocitySamples = 5;
float m_VelSamples[kVelocitySamples] = {};
int32_t m_VelSampleIdx = 0;
int32_t m_VelSampleCount = 0;
// Computed state
float m_ContentSize = 0.0f;
int32_t m_FirstVisibleIndex = 0;
int32_t m_LastVisibleIndex = 0;
// Callback (Template mode)
ScrollItemCallback m_OnBindItem;
// Input handlers (registered on InputCollider in Start()).
// Pointer x/y are world meters (input FFI contract).
void HandlePointerDown(float x, float y);
void HandlePointerMove(float x, float y);
void HandlePointerUp(float x, float y);
// Helpers
float MeasureChildSize(DekiObject* child) const;
int32_t GetTotalSlotCount() const;
void CalculateContentSize();
void AssignSlots();
float GetMaxScrollOffset() const;
void ClampScrollOffset();
DekiObject* CloneTemplate(DekiObject* tmpl, const char* name);
void CancelChildInput(DekiObject* obj);
};
// Generated property metadata (after class definition for offsetof)
#include "generated/ScrollComponent.gen.h"