1 /*
2 * Copyright (c) 2023-2025 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 #include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h"
17
18 #include "core/components_ng/pattern/grid/grid_item_pattern.h"
19 #include "core/components_ng/pattern/grid/irregular/grid_layout_utils.h"
20
21 namespace OHOS::Ace::NG {
GridIrregularFiller(GridLayoutInfo * info,LayoutWrapper * wrapper)22 GridIrregularFiller::GridIrregularFiller(GridLayoutInfo* info, LayoutWrapper* wrapper) : info_(info), wrapper_(wrapper)
23 {}
24
InitPos(int32_t lineIdx)25 int32_t GridIrregularFiller::InitPos(int32_t lineIdx)
26 {
27 posX_ = -1;
28 posY_ = lineIdx;
29 return info_->FindEndIdx(lineIdx - 1).itemIdx;
30 }
31
32 using Result = GridIrregularFiller::FillResult;
Fill(const FillParameters & params,float targetLen,int32_t startingLine)33 Result GridIrregularFiller::Fill(const FillParameters& params, float targetLen, int32_t startingLine)
34 {
35 startingLine = std::max(0, startingLine);
36 int32_t idx = InitPos(startingLine);
37 // no gap on first row
38 float len = -params.mainGap;
39 auto childrenCount = info_->GetChildrenCount();
40 while (idx < (childrenCount - 1)) {
41 int32_t prevRow = posY_;
42 if (!FindNextItem(++idx)) {
43 FillOne(idx);
44 }
45 // (posY_ > prevRow) implies that the previous row has been filled
46
47 int32_t row = prevRow;
48 if (UpdateLength(len, targetLen, row, posY_, params.mainGap)) {
49 return { len, row, idx - 1 };
50 }
51
52 MeasureItem(params, idx, posX_, posY_, false);
53 }
54
55 if (info_->lineHeightMap_.empty()) {
56 return {};
57 }
58 // last child reached
59 int32_t lastRow = info_->lineHeightMap_.rbegin()->first;
60 if (UpdateLength(len, targetLen, posY_, lastRow + 1, params.mainGap)) {
61 return { len, posY_, idx };
62 }
63 return { len, lastRow, idx };
64 }
65
FillToTarget(const FillParameters & params,int32_t targetIdx,int32_t startingLine)66 void GridIrregularFiller::FillToTarget(const FillParameters& params, int32_t targetIdx, int32_t startingLine)
67 {
68 if (startingLine < 0) {
69 startingLine = 0;
70 }
71 if (targetIdx >= info_->GetChildrenCount()) {
72 targetIdx = info_->GetChildrenCount() - 1;
73 }
74 int32_t idx = InitPos(startingLine);
75 while (idx < targetIdx) {
76 if (!FindNextItem(++idx)) {
77 FillOne(idx);
78 }
79 MeasureItem(params, idx, posX_, posY_, false);
80 }
81 }
82
FitItem(const decltype(GridLayoutInfo::gridMatrix_) ::iterator & it,int32_t itemWidth)83 int32_t GridIrregularFiller::FitItem(const decltype(GridLayoutInfo::gridMatrix_)::iterator& it, int32_t itemWidth)
84 {
85 if (it == info_->gridMatrix_.end()) {
86 // empty row, can fit
87 return 0;
88 }
89
90 if (static_cast<int32_t>(it->second.size()) + itemWidth > info_->crossCount_) {
91 // not enough space
92 return -1;
93 }
94
95 for (int i = 0; i <= info_->crossCount_ - itemWidth; ++i) {
96 bool flag = true;
97 for (int j = 0; j < itemWidth; ++j) {
98 if (it->second.find(i + j) != it->second.end()) {
99 // spot already filled
100 flag = false;
101 break;
102 }
103 }
104
105 if (flag) {
106 return i;
107 }
108 }
109 return -1;
110 }
111
FillOne(const int32_t idx)112 void GridIrregularFiller::FillOne(const int32_t idx)
113 {
114 int32_t row = posY_;
115
116 auto size = GridLayoutUtils::GetItemSize(info_, wrapper_, idx);
117
118 auto it = info_->gridMatrix_.find(row);
119 int32_t col = FitItem(it, size.columns);
120 while (col == -1) {
121 // can't fill at end, find the next available line
122 it = info_->gridMatrix_.find(++row);
123 col = FitItem(it, size.columns);
124 }
125
126 if (it == info_->gridMatrix_.end()) {
127 // create a new line
128 info_->gridMatrix_[row] = {};
129 }
130
131 // top left square should be set to [idx], the rest to -[idx]
132 for (int32_t r = 0; r < size.rows; ++r) {
133 for (int32_t c = 0; c < size.columns; ++c) {
134 info_->gridMatrix_[row + r][col + c] = -idx;
135 }
136 }
137
138 info_->gridMatrix_[row][col] = idx;
139
140 posY_ = row;
141 posX_ = col;
142 }
143
FindNextItem(int32_t target)144 bool GridIrregularFiller::FindNextItem(int32_t target)
145 {
146 const auto& mat = info_->gridMatrix_;
147 // start from first cross everytime, for the current target might be before the previous target
148 posX_ = -1;
149 while (AdvancePos()) {
150 if (mat.at(posY_).at(posX_) == target) {
151 return true;
152 }
153 }
154 // to handle empty tiles in the middle of matrix, check next row
155 auto nextRow = mat.find(posY_ + 1);
156 while (nextRow != mat.end()) {
157 for (const auto [col, item] : nextRow->second) {
158 if (item == target) {
159 posY_ = nextRow->first;
160 posX_ = col;
161 return true;
162 }
163 }
164 ++nextRow;
165 }
166 return false;
167 }
168
AdvancePos()169 bool GridIrregularFiller::AdvancePos()
170 {
171 ++posX_;
172 if (posX_ == info_->crossCount_) {
173 // go to next row
174 ++posY_;
175 posX_ = 0;
176 }
177
178 const auto& mat = info_->gridMatrix_;
179 if (mat.find(posY_) == mat.end()) {
180 return false;
181 }
182
183 const auto& row = mat.at(posY_);
184 return row.find(posX_) != row.end();
185 }
186
UpdateLength(float & len,float targetLen,int32_t & row,int32_t rowBound,float mainGap) const187 bool GridIrregularFiller::UpdateLength(float& len, float targetLen, int32_t& row, int32_t rowBound, float mainGap) const
188 {
189 for (; row < rowBound; ++row) {
190 auto lineHeightIt = info_->lineHeightMap_.find(row);
191 if (lineHeightIt == info_->lineHeightMap_.end()) {
192 TAG_LOGW(AceLogTag::ACE_GRID, "line height at row %{public}d not prepared after forward measure", posY_);
193 continue;
194 }
195 len += lineHeightIt->second + mainGap;
196 if (GreatOrEqual(len, targetLen)) {
197 return true;
198 }
199 }
200 return false;
201 }
202
MeasureItem(const FillParameters & params,int32_t itemIdx,int32_t col,int32_t row,bool isCache)203 std::pair<float, LayoutConstraintF> GridIrregularFiller::MeasureItem(
204 const FillParameters& params, int32_t itemIdx, int32_t col, int32_t row, bool isCache)
205 {
206 auto props = AceType::DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
207 auto constraint = props->CreateChildConstraint();
208 auto child = wrapper_->GetOrCreateChildByIndex(itemIdx, !isCache, isCache);
209 CHECK_NULL_RETURN(child, {});
210
211 const auto itemSize = GridLayoutUtils::GetItemSize(info_, wrapper_, itemIdx);
212 float crossLen = 0.0f;
213 for (int32_t i = 0; i < itemSize.columns; ++i) {
214 crossLen += params.crossLens[i + col];
215 }
216 crossLen += params.crossGap * (itemSize.columns - 1);
217 constraint.percentReference.SetCrossSize(crossLen, info_->axis_);
218 if (info_->axis_ == Axis::VERTICAL) {
219 constraint.maxSize = SizeF { crossLen, Infinity<float>() };
220 constraint.parentIdealSize = OptionalSizeF(crossLen, std::nullopt);
221 } else {
222 constraint.maxSize = SizeF { Infinity<float>(), crossLen };
223 constraint.parentIdealSize = OptionalSizeF(std::nullopt, crossLen);
224 }
225
226 if (isCache) {
227 child->SetActive();
228 }
229 child->Measure(constraint);
230 SetItemInfo(child, itemIdx, row, col, itemSize);
231
232 float childHeight = child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_);
233 // spread height to each row.
234 float heightPerRow = (childHeight - (params.mainGap * (itemSize.rows - 1))) / itemSize.rows;
235 for (int32_t i = 0; i < itemSize.rows; ++i) {
236 info_->lineHeightMap_[row + i] = std::max(info_->lineHeightMap_[row + i], heightPerRow);
237 }
238 return { childHeight, constraint };
239 }
240
InitPosToLastItem(int32_t lineIdx)241 int32_t GridIrregularFiller::InitPosToLastItem(int32_t lineIdx)
242 {
243 auto res = info_->FindEndIdx(lineIdx);
244 posX_ = res.x;
245 posY_ = std::max(0, res.y);
246 return res.itemIdx;
247 }
248
FillMatrixOnly(int32_t targetIdx)249 int32_t GridIrregularFiller::FillMatrixOnly(int32_t targetIdx)
250 {
251 if (targetIdx >= info_->GetChildrenCount()) {
252 targetIdx = info_->GetChildrenCount() - 1;
253 }
254 int32_t idx = InitPosToLastItem(static_cast<int32_t>(info_->gridMatrix_.size()) - 1);
255 while (idx < targetIdx) {
256 if (!FindNextItem(++idx)) {
257 FillOne(idx);
258 }
259 }
260 return posY_;
261 }
262
FillMatrixByLine(int32_t startingLine,int32_t targetLine)263 int32_t GridIrregularFiller::FillMatrixByLine(int32_t startingLine, int32_t targetLine)
264 {
265 int32_t idx = InitPosToLastItem(startingLine);
266 auto childrenCount = info_->GetChildrenCount();
267 while (posY_ < targetLine && idx < (childrenCount - 1)) {
268 if (!FindNextItem(++idx)) {
269 FillOne(idx);
270 }
271 }
272 return idx;
273 }
274
MeasureBackward(const FillParameters & params,float targetLen,int32_t startingLine)275 float GridIrregularFiller::MeasureBackward(const FillParameters& params, float targetLen, int32_t startingLine)
276 {
277 float len = 0.0f;
278 posY_ = startingLine;
279 std::unordered_set<int32_t> measured;
280
281 for (; posY_ >= 0 && LessNotEqual(len, targetLen); --posY_) {
282 BackwardImpl(measured, params);
283 auto lineHeightIt = info_->lineHeightMap_.find(posY_);
284 if (lineHeightIt == info_->lineHeightMap_.end()) {
285 TAG_LOGW(AceLogTag::ACE_GRID, "line height at row %{public}d not prepared after backward measure", posY_);
286 continue;
287 }
288 len += params.mainGap + lineHeightIt->second;
289 }
290 return len;
291 }
292
MeasureBackwardToTarget(const FillParameters & params,int32_t targetLine,int32_t startingLine)293 void GridIrregularFiller::MeasureBackwardToTarget(
294 const FillParameters& params, int32_t targetLine, int32_t startingLine)
295 {
296 if (targetLine < 0) {
297 return;
298 }
299 posY_ = startingLine;
300
301 std::unordered_set<int32_t> measured;
302 for (; posY_ >= targetLine; --posY_) {
303 BackwardImpl(measured, params);
304 }
305 }
306
MeasureLineWithIrregulars(const FillParameters & params,const int32_t line)307 void GridIrregularFiller::MeasureLineWithIrregulars(const FillParameters& params, const int32_t line)
308 {
309 if (line == 0) {
310 return;
311 }
312 const auto it = info_->gridMatrix_.find(line);
313 if (it == info_->gridMatrix_.end()) {
314 return;
315 }
316 std::unordered_set<int32_t> visited;
317 int32_t topRow = line;
318 for (const auto& [c, itemIdx] : it->second) {
319 if (itemIdx == 0) {
320 topRow = 0;
321 break;
322 }
323 if (itemIdx < 0 && !visited.count(std::abs(itemIdx))) {
324 topRow = std::min(FindItemTopRow(it->first, c), topRow);
325 }
326 visited.insert(std::abs(itemIdx));
327 }
328 if (topRow < line) {
329 MeasureBackwardToTarget(params, topRow, line);
330 }
331 }
332
BackwardImpl(std::unordered_set<int32_t> & measured,const FillParameters & params)333 void GridIrregularFiller::BackwardImpl(std::unordered_set<int32_t>& measured, const FillParameters& params)
334 {
335 auto it = info_->gridMatrix_.find(posY_);
336 if (it == info_->gridMatrix_.end()) {
337 TAG_LOGW(AceLogTag::ACE_GRID, "positionY %{public}d not found in matrix in backward measure.", posY_);
338 return;
339 }
340 const auto& row = it->second;
341 for (const auto& colIt : row) {
342 const int32_t& c = colIt.first;
343 const int32_t itemIdx = std::abs(colIt.second);
344 if (measured.count(itemIdx)) {
345 continue;
346 }
347
348 const int32_t topRow = FindItemTopRow(posY_, c);
349 MeasureItem(params, itemIdx, c, topRow, false);
350 // measure irregular items only once from the bottom-left tile
351 measured.insert(itemIdx);
352 }
353 }
354
FindItemTopRow(int32_t row,int32_t col) const355 int32_t GridIrregularFiller::FindItemTopRow(int32_t row, int32_t col) const
356 {
357 if (info_->gridMatrix_.at(row).at(col) == 0) {
358 return 0;
359 }
360
361 while (info_->gridMatrix_.at(row).at(col) < 0) {
362 --row;
363 }
364 return row;
365 }
366
SetItemInfo(const RefPtr<LayoutWrapper> & item,int32_t idx,int32_t row,int32_t col,GridItemSize size)367 void GridIrregularFiller::SetItemInfo(
368 const RefPtr<LayoutWrapper>& item, int32_t idx, int32_t row, int32_t col, GridItemSize size)
369 {
370 CHECK_NULL_VOID(item);
371 if (info_->axis_ == Axis::HORIZONTAL) {
372 std::swap(row, col);
373 std::swap(size.rows, size.columns);
374 }
375 auto pattern = item->GetHostNode()->GetPattern<GridItemPattern>();
376 CHECK_NULL_VOID(pattern);
377 auto props = pattern->GetLayoutProperty<GridItemLayoutProperty>();
378 CHECK_NULL_VOID(props);
379 props->UpdateIndex(idx);
380 props->UpdateMainIndex(row);
381 props->UpdateCrossIndex(col);
382
383 if (size.rows == 1 && size.columns == 1) {
384 pattern->ResetGridItemInfo();
385 return;
386 }
387 pattern->SetIrregularItemInfo({ .mainIndex = row,
388 .crossIndex = col,
389 .mainSpan = size.rows,
390 .crossSpan = size.columns,
391 .mainStart = row,
392 .mainEnd = row + size.rows - 1,
393 .crossStart = col,
394 .crossEnd = col + size.columns - 1 });
395 }
396 } // namespace OHOS::Ace::NG
397