1 /*
2  * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_LAYOUT_INFO_H
17 #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_LAYOUT_INFO_H
18 
19 #include <map>
20 #include <optional>
21 
22 #include "base/geometry/axis.h"
23 #include "base/geometry/ng/rect_t.h"
24 #include "core/components/scroll/scroll_controller_base.h"
25 #include "core/components_ng/pattern/grid/grid_layout_options.h"
26 #include "core/components_ng/property/layout_constraint.h"
27 
28 namespace OHOS::Ace::NG {
29 
30 struct GridPredictLayoutParam {
31     LayoutConstraintF layoutConstraint;
32     std::map<int32_t, float> itemsCrossSizes;
33     float crossGap = 0.0f;
34 };
35 
36 struct GridPreloadItem {
GridPreloadItemGridPreloadItem37     explicit GridPreloadItem(int32_t idx) : idx(idx) {}
GridPreloadItemGridPreloadItem38     GridPreloadItem(int32_t idx, bool buildOnly) : idx(idx), buildOnly(buildOnly) {}
39 
40     bool operator==(const GridPreloadItem& other) const
41     {
42         return idx == other.idx && buildOnly == other.buildOnly;
43     }
44 
45     int32_t idx = -1;
46     bool buildOnly = false; // true if item only needs to be created, not measure / layout
47 };
48 
49 /**
50  * @brief callback to build a grid item at [itemIdx] in an IdleTask
51  * @returns true if the built item needs Grid to perform a layout.
52  */
53 using BuildGridItemCallback = std::function<bool(const RefPtr<FrameNode>& host, int32_t itemIdx)>;
54 
55 constexpr int32_t EMPTY_JUMP_INDEX = -2;
56 constexpr int32_t JUMP_TO_BOTTOM_EDGE = -3;
57 constexpr float HALF = 0.5f;
58 
59 // Try not to add more variables in [GridLayoutInfo] because the more state variables, the more problematic and the
60 // harder it is to maintain
61 struct GridLayoutInfo {
62     /**
63      * @param regular running regular/irregular layout. For compatibility.
64      * Because in regular we used to add starting lines that are above viewport.
65      *
66      * @return height of all lines in viewport.
67      */
68     float GetTotalHeightOfItemsInView(float mainGap, bool regular = true) const;
69     /**
70      * @brief skip starting lines that are outside viewport in LayoutIrregular
71      *
72      * @return [iterator to the first line in view, offset of that first line]
73      */
74     using HeightMapIt = std::map<int32_t, float>::const_iterator;
75     std::pair<HeightMapIt, float> SkipLinesAboveView(float mainGap) const;
76 
UpdateStartIndexByStartLineGridLayoutInfo77     void UpdateStartIndexByStartLine()
78     {
79         auto startLine = gridMatrix_.find(startMainLineIndex_);
80         if (startLine == gridMatrix_.end()) {
81             return;
82         }
83         if (startLine->second.empty()) {
84             return;
85         }
86         startIndex_ = startLine->second.begin()->second;
87     }
88 
89     void UpdateStartIndexForExtralOffset(float mainGap, float mainSize);
90 
91     void UpdateEndLine(float mainSize, float mainGap);
92     // for overScroll at top
93     void UpdateEndIndex(float overScrollOffset, float mainSize, float mainGap);
94     bool IsOutOfStart() const;
95     /**
96      * @brief Determine out of boundary condition for overScroll
97      *
98      * @param irregular whether running irregular layout.
99      * @return true if the end of content is above viewport.
100      */
101     bool IsOutOfEnd(float mainGap, bool irregular) const;
102 
103     void SwapItems(int32_t itemIndex, int32_t insertIndex);
104     int32_t GetOriginalIndex() const;
105     void ClearDragState();
106 
GetAverageLineHeightGridLayoutInfo107     float GetAverageLineHeight()
108     {
109         float totalHeight = 0;
110         int32_t totalRow = 0;
111         for (const auto& record : lineHeightMap_) {
112             if (record.second > 0) {
113                 totalRow++;
114                 totalHeight += record.second;
115             }
116         }
117         return totalRow > 0 ? totalHeight / totalRow : 0;
118     }
119 
120     // should only be used when all children of Grid are in gridMatrix_
GetStartLineOffsetGridLayoutInfo121     float GetStartLineOffset(float mainGap) const
122     {
123         float totalHeight = 0;
124         for (auto iter = lineHeightMap_.begin(); iter != lineHeightMap_.end() && iter->first < startMainLineIndex_;
125              ++iter) {
126             totalHeight += (iter->second + mainGap);
127         }
128         return totalHeight - currentOffset_;
129     }
130 
131     float GetTotalLineHeight(float mainGap, bool removeLastGap = true) const
132     {
133         float totalHeight = 0.0f;
134         for (auto iter : lineHeightMap_) {
135             totalHeight += (iter.second + mainGap);
136         }
137         return (removeLastGap) ? totalHeight - mainGap : totalHeight;
138     }
139 
140     /**
141      * @brief set up jumpIndex_ and align_ to jump to the bottom edge of content.
142      */
143     void PrepareJumpToBottom();
144 
145     /**
146      * @brief optimized function (early exit) to compare total height to [other].
147      * @param other height to compare to.
148      * @return true if total height is less than [other].
149      */
150     bool HeightSumSmaller(float other, float mainGap) const;
151 
152     /**
153      * @return height sum of lines in range [startLine, endLine).
154      */
155     float GetHeightInRange(int32_t startLine, int32_t endLine, float mainGap) const;
156 
157     struct EndIndexInfo {
158         int32_t itemIdx = -1; /**< Index of the last item. */
159         int32_t y = -1;       /**< Main-axis position (line index) of the item. */
160         int32_t x = -1;       /**< Cross-axis position (column index) of the item. */
161     };
162     /**
163      * @brief Traverse the matrix backward to find the last item index, starting from Line [endLine].
164      *
165      * Intended to work on irregular layout.
166      * @param endLine index of the line to start traversing.
167      * @return last item index above endLine (inclusive) and the position it resides in.
168      */
169     EndIndexInfo FindEndIdx(int32_t endLine) const;
170 
171     /**
172      * REQUIRES: Item is between startIndex_ and endIndex_. Otherwise, the result is undefined.
173      *
174      * @param line starting line of the item.
175      * @param mainGap The gap between lines.
176      * @return position of the item's top edge relative to the viewport.
177      */
GetItemTopPosGridLayoutInfo178     inline float GetItemTopPos(int32_t line, float mainGap) const
179     {
180         return currentOffset_ + GetHeightInRange(startMainLineIndex_, line, mainGap);
181     }
182 
183     /**
184      * REQUIRES: Item is between startIndex_ and endIndex_. Otherwise, the result is undefined.
185      *
186      * @param line starting line of the item.
187      * @param itemHeight The number of rows the item occupies.
188      * @param mainGap The gap between items in the main axis.
189      * @return position of the item's bottom edge relative to the viewport.
190      */
GetItemBottomPosGridLayoutInfo191     inline float GetItemBottomPos(int32_t line, int32_t itemHeight, float mainGap) const
192     {
193         return currentOffset_ + GetHeightInRange(startMainLineIndex_, line + itemHeight, mainGap) - mainGap;
194     }
195 
196     /**
197      * @brief Perform a binary search to find item with [index] in the gridMatrix_.
198      *
199      * @param index target item index
200      * @return iterator to that item, or map::end if not found.
201      */
202     std::map<int32_t, std::map<int32_t, int32_t>>::const_iterator FindInMatrix(int32_t index) const;
203 
204     /**
205      * @param itemIdx
206      * @return position [col, row] of the item. [-1, -1] if item is not in matrix.
207      */
208     std::pair<int32_t, int32_t> GetItemPos(int32_t itemIdx) const;
209 
210     /**
211      * @brief Tries to find the item between startMainLine and endMainLine.
212      *
213      * @param target The target item to find.
214      * @return The line index and column index of the found item.
215      */
216     std::pair<int32_t, int32_t> FindItemInRange(int32_t target) const;
217 
218     /**
219      * @brief Find the offset and line index of an item's center point.
220      *
221      * @param startLine starting line index of this item.
222      * @param lineCnt number of rows the item occupies.
223      * @return [lineIdx, offset relative to this line] of the center point.
224      */
225     std::pair<int32_t, float> FindItemCenter(int32_t startLine, int32_t lineCnt, float mainGap) const;
226 
227     /**
228      * @brief clears lineHeightMap_ and gridMatrix_ starting from line [idx]
229      *
230      * @param idx starting line index
231      */
232     void ClearMapsToEnd(int32_t idx);
233 
234     /**
235      * @brief clears lineHeightMap_ and gridMatrix_ in range [0, idx)
236      *
237      * @param idx ending line index, exclusive.
238      */
239     void ClearMapsFromStart(int32_t idx);
240 
241     /**
242      * @brief clears lineHeightMap_ starting from line [idx]
243      *
244      * @param idx starting line index
245      */
246     void ClearHeightsToEnd(int32_t idx);
247 
248     /**
249      * @brief clear gridMatrix_ in range [idx, end)
250      *
251      * REQUIRES: idx and lineIdx have to match each other.
252      * @param idx item index to start clearing from.
253      * @param lineIdx already-found line index of the target item.
254      */
255     void ClearMatrixToEnd(int32_t idx, int32_t lineIdx);
256 
ResetPositionFlagsGridLayoutInfo257     void ResetPositionFlags()
258     {
259         reachEnd_ = false;
260         reachStart_ = false;
261         offsetEnd_ = false;
262     }
263 
IsResettedGridLayoutInfo264     bool IsResetted() const
265     {
266         return startIndex_ != 0 && gridMatrix_.empty();
267     }
268 
SetScrollAlignGridLayoutInfo269     void SetScrollAlign(ScrollAlign align)
270     {
271         scrollAlign_ = align;
272     }
273 
274     float GetContentOffset(float mainGap) const;
275     /**
276      * @brief Get the total height of grid content. Use estimation when lineHeights are not available. Can handle
277      * bigItems.
278      *
279      * @param mainGap
280      * @return total height
281      */
282     float GetContentHeight(float mainGap) const;
283     float GetContentOffset(const GridLayoutOptions& options, float mainGap) const;
284 
285     /**
286      * @brief Get the content height of Grid in range [0, endIdx).
287      *
288      * IF: Irregular items always take up the whole line (no getSizeByIdx callback).
289      * THEN: Assumes that all irregular lines have the same height, and all other regular lines have the same height.
290      * ELSE: Call a more versatile algorithm.
291      * REQUIRES:
292      * 1. all irregular lines must have the same height.
293      * 2. all regular items must have the same height.
294      *
295      * @param options contains irregular item.
296      * @param endIdx ending item index (exclusive).
297      * @param mainGap gap between lines.
298      * @return total height of the content.
299      */
300     float GetContentHeight(const GridLayoutOptions& options, int32_t endIdx, float mainGap) const;
301     void SkipStartIndexByOffset(const GridLayoutOptions& options, float mainGap);
302     float GetCurrentLineHeight() const;
303 
304     /**
305      * @brief Get Content Offset when using irregular layout.
306      */
307     float GetIrregularOffset(float mainGap) const;
308     /**
309      * @brief Get total content height when using irregular layout.
310      */
311     float GetIrregularHeight(float mainGap) const;
312 
313     bool GetLineIndexByIndex(int32_t targetIndex, int32_t& targetLineIndex) const;
314     float GetTotalHeightFromZeroIndex(int32_t targetLineIndex, float mainGap) const;
315 
316     /**
317      * @brief Calculate the distance from content's end to the viewport bottom.
318      *
319      * REQUIRES: currentOffset_ is valid from last layout.
320      * @param mainSize of the viewport.
321      * @param heightInView total height of items inside the viewport.
322      * @param mainGap gap between lines along the main axis.
323      * @return Positive when content's end is below viewport. Return [mainSize] if last line is not in viewport.
324      */
325     float GetDistanceToBottom(float mainSize, float heightInView, float mainGap) const;
326 
327     /**
328      * @brief Transforms scrollAlign_ into other ScrollAlign values, based on current position of
329      * target item.
330      *
331      * @param height number of rows the item occupies.
332      * @param mainSize The main-axis length of the grid.
333      * @return ScrollAlign value transformed from AUTO.
334      */
335     ScrollAlign TransformAutoScrollAlign(int32_t itemIdx, int32_t height, float mainSize, float mainGap) const;
336 
337     /**
338      * @param targetIdx target item's index.
339      * @param height number of rows the item occupies.
340      * @return item position to scroll to through animation.
341      */
342     float GetAnimatePosIrregular(int32_t targetIdx, int32_t height, ScrollAlign align, float mainGap) const;
343 
344     bool GetGridItemAnimatePos(const GridLayoutInfo& currentGridLayoutInfo, int32_t targetIndex, ScrollAlign align,
345         float mainGap, float& targetPos);
346 
347     using MatIter = std::map<int32_t, std::map<int32_t, int32_t>>::const_iterator;
348     MatIter FindStartLineInMatrix(MatIter iter, int32_t index) const;
349     void ClearHeightsFromMatrix(int32_t lineIdx);
350 
351     void UpdateDefaultCachedCount();
352 
353     Axis axis_ = Axis::VERTICAL;
354 
355     float currentOffset_ = 0.0f; // offset on the current top GridItem on [startMainLineIndex_]
356     float prevOffset_ = 0.0f;
357     float currentHeight_ = 0.0f; // height from first item to current top GridItem on [startMainLineIndex_]
358     float prevHeight_ = 0.0f;
359     float lastMainSize_ = 0.0f;
360     float lastCrossSize_ = 0.0f;
361     float totalHeightOfItemsInView_ = 0.0f;
362     float avgLineHeight_ = 0.0f;
363 
364     // additional padding to accommodate navigation bar when SafeArea is expanded
365     float contentEndPadding_ = 0.0f;
366 
367     std::optional<int32_t> lastCrossCount_;
368     // index of first and last GridItem in viewport
369     int32_t startIndex_ = 0;
370     int32_t endIndex_ = -1;
371 
372     // index of first row and last row in viewport (assuming it's a vertical Grid)
373     int32_t startMainLineIndex_ = 0;
374     int32_t endMainLineIndex_ = 0;
375 
376     int32_t jumpIndex_ = EMPTY_JUMP_INDEX;
377     std::optional<float> extraOffset_;
378     int32_t crossCount_ = 0;
379     int32_t childrenCount_ = 0;
380     ScrollAlign scrollAlign_ = ScrollAlign::AUTO;
381 
382     // Map structure: [mainIndex, [crossIndex, index]],
383     // when vertical, mainIndex is rowIndex and crossIndex is columnIndex.
384     std::map<int32_t, std::map<int32_t, int32_t>> gridMatrix_;
385     // in vertical grid, this map is like: [rowIndex: rowHeight]
386     std::map<int32_t, float> lineHeightMap_;
387 
388     // Map structure: [index, last cell]
389     std::map<int32_t, int32_t> irregularItemsPosition_;
390 
391     // rect of grid item dragged in
392     RectF currentRect_;
393 
394     bool reachEnd_ = false; // true if last GridItem appears in the viewPort
395     bool reachStart_ = false;
396 
397     bool offsetEnd_ = false; // true if content bottom is truly reached
398 
399     // Grid has GridItem whose columnEnd - columnStart > 0
400     bool hasBigItem_;
401 
402     // Grid has GridItem whose rowEnd - rowStart > 0
403     bool hasMultiLineItem_;
404     // false when offset is updated but layout hasn't happened, so data is out of sync
405     bool synced_ = false;
406 
407     std::optional<int32_t> targetIndex_;
408 
409     std::map<int32_t, bool> irregularLines_;
410 
411     bool clearStretch_ = false;
412 
413     // default cached count
414     int32_t defCachedCount_ = 1;
415 
416 private:
417     float GetCurrentOffsetOfRegularGrid(float mainGap) const;
418     float GetContentHeightOfRegularGrid(float mainGap) const;
419     int32_t GetItemIndexByPosition(int32_t position);
420     int32_t GetPositionByItemIndex(int32_t itemIndex);
421     void MoveItemsBack(int32_t from, int32_t to, int32_t itemIndex);
422     void MoveItemsForward(int32_t from, int32_t to, int32_t itemIndex);
423     void GetLineHeights(
424         const GridLayoutOptions& options, float mainGap, float& regularHeight, float& irregularHeight) const;
425 
426     /**
427      * @brief Find the number of GridItems in range [startLine, endLine].
428      *
429      * REQUIRES: gridMatrix_ is valid in range [startLine, endLine].
430      * @return number of GridItems
431      */
432     int32_t FindItemCount(int32_t startLine, int32_t endLine) const;
433 
434     int32_t currentMovingItemPosition_ = -1;
435     std::map<int32_t, int32_t> positionItemIndexMap_;
436     float lastIrregularMainSize_ = 0.0f; // maybe no irregular item in current gridMatrix_
437     float lastRegularMainSize_ = 0.0f;
438 };
439 
440 } // namespace OHOS::Ace::NG
441 #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_LAYOUT_ALGORITHM_H
442