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