Aurore
A Switch-like interface for your app, on every screen
Loading...
Searching...
No Matches
recycling_grid.hpp
Go to the documentation of this file.
1/*
2 Copyright 2022 xfangfang
3 Copyright 2023 dragonflylee and the Switchfin contributors
4 Copyright 2026 JigSawFr
5
6 Adapted from Switchfin's recycling_grid and h_recycling
7 (https://github.com/dragonflylee/switchfin, Apache-2.0).
8
9 Licensed under the Apache License, Version 2.0 (the "License");
10 you may not use this file except in compliance with the License.
11 You may obtain a copy of the License at
12
13 http://www.apache.org/licenses/LICENSE-2.0
14
15 Unless required by applicable law or agreed to in writing, software
16 distributed under the License is distributed on an "AS IS" BASIS,
17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 See the License for the specific language governing permissions and
19 limitations under the License.
20*/
21
22#pragma once
23
24#include <functional>
25#include <map>
26#include <string>
27#include <vector>
28#include <aurore/core/box.hpp>
31
32namespace aurore
33{
34
35class RecyclingView;
36
37// A cell of a RecyclingGrid or an HRecyclerFrame. Only the cells on screen
38// (and a line ahead on each side) exist: the others are queued, and given
39// back by dequeueReusableCell() to show another item.
40class RecyclingGridItem : public Box
41{
42 public:
44
45 // The item this cell shows, in the data source.
46 size_t getIndex() const { return this->index; }
47
48 // Internal: set by the recycler when the cell is placed.
49 void setIndex(size_t value) { this->index = value; }
50
51 // The identifier the cell was registered with (registerCell()).
52 std::string reuseIdentifier;
53
54 // Called by dequeueReusableCell() before the data source fills the cell
55 // again: reset what one item set and another might not.
56 virtual void prepareForReuse() { }
57
58 // Called when the cell leaves the screen and is queued: let go of what
59 // is heavy (a picture's texture, a request in flight).
60 virtual void cacheForReuse() { }
61
62 private:
63 size_t index = 0;
64};
65
67{
68 public:
69 virtual ~RecyclingGridDataSource() = default;
70
71 // How many items there are now.
72 virtual size_t getItemCount() { return 0; }
73
74 // A cell showing the item at `index`: dequeueReusableCell(), then fill it.
75 virtual RecyclingGridItem* cellForRow(RecyclingView* recycler, size_t index) { return nullptr; }
76
77 // The item at `index` was clicked (A, a tap, a click).
78 virtual void onItemSelected(View* recycler, size_t index) { }
79
80 // Forget the items (RecyclingView::clearData()).
81 virtual void clearData() { }
82};
83
84// What a RecyclingGrid and an HRecyclerFrame share: the cells, their queue,
85// the data source, where each item goes and the focus between them. Items
86// are laid out in lines of `spanCount` along the scrolling axis: rows of
87// columns in a RecyclingGrid, columns of rows in an HRecyclerFrame. Every
88// item has the same size.
90{
91 public:
92 virtual ~RecyclingView();
93
94 // Registers how to build a cell for `identifier`.
95 void registerCell(std::string identifier, std::function<RecyclingGridItem*()> allocation);
96
97 // A queued cell for `identifier`, or a new one; prepareForReuse() is
98 // called on it either way.
99 RecyclingGridItem* dequeueReusableCell(std::string identifier);
100
101 // The data source; with `deleteDataSource`, the recycler deletes it when
102 // it is replaced or when the recycler goes.
103 void setDataSource(RecyclingGridDataSource* source, bool deleteDataSource = true);
104 RecyclingGridDataSource* getDataSource() const { return this->dataSource; }
105
106 // Builds the cells again from the data source, scrolled to the default
107 // cell (setDefaultCellFocus()); the focus, if it was in the recycler,
108 // goes to that cell.
110
111 // Items were added at the end: the content grows, the cells and the
112 // scrolling stay, and the end-reached callback may fire again. Fewer
113 // items than before reload everything.
115
116 // Calls the data source's clearData(), then reloads.
117 void clearData();
118
119 // Called once when the last item comes into view (with preFetchLine
120 // lines ahead): load the next page there, then notifyDataChanged().
121 // It fires again after notifyDataChanged(), reloadData(),
122 // setDataSource() or forceRequestNextPage().
123 void onEndReached(std::function<void()> callback) { this->endReachedCallback = callback; }
124 void onNextPage(std::function<void()> callback) { this->onEndReached(callback); }
125 void forceRequestNextPage() { this->endRequested = false; }
126
127 // Scrolls to the item at `index` and makes it the one focused when the
128 // focus enters the recycler.
129 void selectRowAt(size_t index, bool animated);
130
131 // The cell showing the item at `index`, if it is on screen. Only valid
132 // until the next frame: cells are reused.
134
135 void setDefaultCellFocus(size_t index) { this->defaultCellFocus = index; }
136 size_t getDefaultCellFocus() const { return this->defaultCellFocus; }
137
138 size_t getItemCount();
139
140 // Lines of items across the scrolling axis.
141 int getSpanCount() const { return this->span; }
142
143 // The frame: the RecyclingGrid or HRecyclerFrame itself.
144 virtual Box* getFrameBox() = 0;
145
146 // Internal, for the content box.
147 View* getNextCellFocus(FocusDirection direction, View* currentView);
148
149 protected:
150 RecyclingView(Axis scrollAxis);
151
152 Box* contentBox = nullptr;
153
154 virtual float getScrollOffset() = 0;
155 virtual void setScrollOffset(float offset, bool animated) = 0;
156
157 // Places and recycles the cells for the current scrolling.
159
160 // The frame's size changed: the items are laid out again before the
161 // next frame (not from onLayout(), which yoga calls in the middle of its
162 // layout: building cells there moved the frame itself).
164
165 // Applies a pending layoutChanged().
167
168 // onThemeChanged() on the cells kept for reuse (the frame's own
169 // onThemeChanged() reaches the cells shown).
171
172 // The cell the focus enters the recycler on, if any.
174
175 // Padding inside the scrolled content; reloadData() applies it.
177
178 // Lines across the scrolling axis: a fixed count, or as many as fit
179 // lines `minimumLineSize` wide (0: the fixed count).
180 int spanCount = 1;
182
183 // The size of an item along the scrolling axis, the space between two
184 // items, and how many lines are kept ready beyond each end of the view.
185 float itemSize = 240;
186 float itemSpace = 20;
188
189 private:
190 Axis axis;
191 bool layouted = false;
192 bool layoutPending = false;
193 int span = 1;
194 float crossSize = 0; // an item's size across the scrolling axis
195
196 RecyclingGridDataSource* dataSource = nullptr;
197 bool deleteDataSource = false;
198 size_t defaultCellFocus = 0;
199 size_t knownCount = 0;
200
201 std::function<void()> endReachedCallback;
202 bool endRequested = false;
203
204 std::map<std::string, std::vector<RecyclingGridItem*>> queueMap;
205 std::map<std::string, std::function<RecyclingGridItem*()>> allocationMap;
206
207 void computeGeometry();
208 float pitch() const { return this->itemSize + this->itemSpace; }
209 float lineStart(size_t line) const;
210 float scrollTarget(size_t index);
211 void placeCell(RecyclingGridItem* cell, size_t index);
212 RecyclingGridItem* addCell(size_t index);
213 void queueCell(RecyclingGridItem* cell);
214 bool holdsFocus(View* view);
215};
216
217// Its cells' parent: hands the navigation between cells to the recycler.
219{
220 public:
222 View* getNextFocus(FocusDirection direction, View* currentView) override;
223 RecyclingView* getRecycler() const { return this->recycler; }
224
225 private:
226 RecyclingView* recycler;
227};
228
229// A grid that scrolls vertically and only keeps the cells on screen.
230// `spanCount` columns, or as many `columnWidth` wide as fit (then they share
231// what is left); every row `itemHeight` tall, `itemSpace` between items.
232// The focus moves in both directions; it leaves the grid past its edges.
233//
234// XML: spanCount, columnWidth, itemHeight, itemSpace, preFetchLine, and the
235// padding attributes (padding inside the scrolled content).
237{
238 public:
240
241 void draw(NVGcontext* vg, float x, float y, float width, float height, Style style, FrameContext* ctx) override;
242 void onLayout() override;
243 void onThemeChanged() override;
245
246 void setSpanCount(int count);
247 void setColumnWidth(float width);
248 void setItemHeight(float height);
249 void setItemSpace(float space);
250 void setPreFetchLine(int lines);
251
252 void setPadding(float padding) override;
253 void setPadding(float top, float right, float bottom, float left) override;
254 void setPaddingTop(float top) override;
255 void setPaddingRight(float right) override;
256 void setPaddingBottom(float bottom) override;
257 void setPaddingLeft(float left) override;
258
259 static View* create();
260
261 Box* getFrameBox() override { return this; }
262
263 protected:
264 float getScrollOffset() override { return this->getContentOffsetY(); }
265 void setScrollOffset(float offset, bool animated) override { this->setContentOffsetY(offset, animated); }
266
267 private:
268 float oldWidth = -1;
269};
270
271// A row that scrolls horizontally and only keeps the cells on screen:
272// items `itemWidth` wide, `itemSpace` apart, as tall as the row (or
273// `spanCount` rows sharing its height).
274//
275// XML: itemWidth, itemSpace, spanCount, preFetchLine, and the padding
276// attributes (padding inside the scrolled content).
278{
279 public:
281
282 void draw(NVGcontext* vg, float x, float y, float width, float height, Style style, FrameContext* ctx) override;
283 void onLayout() override;
284 void onThemeChanged() override;
286
287 void setSpanCount(int count);
288 void setItemWidth(float width);
289 void setItemSpace(float space);
290 void setPreFetchLine(int lines);
291
292 void setPadding(float padding) override;
293 void setPadding(float top, float right, float bottom, float left) override;
294 void setPaddingTop(float top) override;
295 void setPaddingRight(float right) override;
296 void setPaddingBottom(float bottom) override;
297 void setPaddingLeft(float left) override;
298
299 static View* create();
300
301 Box* getFrameBox() override { return this; }
302
303 protected:
304 float getScrollOffset() override { return this->getContentOffsetX(); }
305 void setScrollOffset(float offset, bool animated) override { this->setContentOffsetX(offset, animated); }
306
307 private:
308 float oldHeight = -1;
309};
310
311} // namespace aurore
Generic FlexBox layout.
Definition box.hpp:74
Definition frame_context.hpp:30
Definition recycling_grid.hpp:278
void setItemWidth(float width)
void setPaddingTop(float top) override
static View * create()
void setPadding(float top, float right, float bottom, float left) override
void setPaddingRight(float right) override
void onLayout() override
void onThemeChanged() override
void setPreFetchLine(int lines)
float getScrollOffset() override
Definition recycling_grid.hpp:304
void draw(NVGcontext *vg, float x, float y, float width, float height, Style style, FrameContext *ctx) override
void setPaddingBottom(float bottom) override
Box * getFrameBox() override
Definition recycling_grid.hpp:301
void setPadding(float padding) override
void setPaddingLeft(float left) override
View * getDefaultFocus() override
void setSpanCount(int count)
void setItemSpace(float space)
void setScrollOffset(float offset, bool animated) override
Definition recycling_grid.hpp:305
Definition h_scrolling_frame.hpp:30
float getContentOffsetX() const
Definition h_scrolling_frame.hpp:69
void setContentOffsetX(float value, bool animated)
Definition recycling_grid.hpp:219
RecyclingView * getRecycler() const
Definition recycling_grid.hpp:223
View * getNextFocus(FocusDirection direction, View *currentView) override
RecyclingGridContentBox(RecyclingView *recycler)
Definition recycling_grid.hpp:67
virtual ~RecyclingGridDataSource()=default
virtual void onItemSelected(View *recycler, size_t index)
Definition recycling_grid.hpp:78
virtual size_t getItemCount()
Definition recycling_grid.hpp:72
virtual RecyclingGridItem * cellForRow(RecyclingView *recycler, size_t index)
Definition recycling_grid.hpp:75
virtual void clearData()
Definition recycling_grid.hpp:81
Definition recycling_grid.hpp:41
virtual void prepareForReuse()
Definition recycling_grid.hpp:56
std::string reuseIdentifier
Definition recycling_grid.hpp:52
virtual void cacheForReuse()
Definition recycling_grid.hpp:60
size_t getIndex() const
Definition recycling_grid.hpp:46
void setIndex(size_t value)
Definition recycling_grid.hpp:49
Definition recycling_grid.hpp:237
void setPaddingTop(float top) override
void setPadding(float top, float right, float bottom, float left) override
Box * getFrameBox() override
Definition recycling_grid.hpp:261
void setColumnWidth(float width)
void setPreFetchLine(int lines)
void setSpanCount(int count)
View * getDefaultFocus() override
void draw(NVGcontext *vg, float x, float y, float width, float height, Style style, FrameContext *ctx) override
void setPaddingBottom(float bottom) override
void onThemeChanged() override
void onLayout() override
void setItemSpace(float space)
void setScrollOffset(float offset, bool animated) override
Definition recycling_grid.hpp:265
void setPadding(float padding) override
float getScrollOffset() override
Definition recycling_grid.hpp:264
void setPaddingRight(float right) override
void setPaddingLeft(float left) override
static View * create()
void setItemHeight(float height)
Definition recycling_grid.hpp:90
void forceRequestNextPage()
Definition recycling_grid.hpp:125
float paddingRight
Definition recycling_grid.hpp:176
float itemSpace
Definition recycling_grid.hpp:186
virtual void setScrollOffset(float offset, bool animated)=0
int preFetchLine
Definition recycling_grid.hpp:187
void registerCell(std::string identifier, std::function< RecyclingGridItem *()> allocation)
void onEndReached(std::function< void()> callback)
Definition recycling_grid.hpp:123
float minimumLineSize
Definition recycling_grid.hpp:181
void setDefaultCellFocus(size_t index)
Definition recycling_grid.hpp:135
float paddingLeft
Definition recycling_grid.hpp:176
virtual float getScrollOffset()=0
void selectRowAt(size_t index, bool animated)
View * getNextCellFocus(FocusDirection direction, View *currentView)
int getSpanCount() const
Definition recycling_grid.hpp:141
float itemSize
Definition recycling_grid.hpp:185
void onNextPage(std::function< void()> callback)
Definition recycling_grid.hpp:124
RecyclingGridItem * dequeueReusableCell(std::string identifier)
int spanCount
Definition recycling_grid.hpp:180
float paddingBottom
Definition recycling_grid.hpp:176
RecyclingView(Axis scrollAxis)
void setDataSource(RecyclingGridDataSource *source, bool deleteDataSource=true)
Box * contentBox
Definition recycling_grid.hpp:152
virtual Box * getFrameBox()=0
RecyclingGridDataSource * getDataSource() const
Definition recycling_grid.hpp:104
float paddingTop
Definition recycling_grid.hpp:176
size_t getDefaultCellFocus() const
Definition recycling_grid.hpp:136
RecyclingGridItem * getGridItemByIndex(size_t index)
Definition scrolling_frame.hpp:45
float getContentOffsetY() const
Definition scrolling_frame.hpp:86
void setContentOffsetY(float value, bool animated)
Simple wrapper around StyleValues for the array operator.
Definition style.hpp:115
Definition view.hpp:232
Definition actions.hpp:25
FocusDirection
Focus direction when navigating.
Definition view.hpp:96
Axis
The direction a box lays its children out in (axis in XML).
Definition box.hpp:51