1 /*
2  * Copyright (c) 2023-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 #include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h"
17 
18 #include "base/geometry/axis.h"
19 #include "base/geometry/ng/size_t.h"
20 #include "core/components_ng/pattern/grid/grid_item_layout_property.h"
21 #include "core/components_ng/pattern/grid/grid_item_pattern.h"
22 #include "core/components_ng/pattern/grid/grid_layout_property.h"
23 #include "core/components_ng/pattern/grid/irregular/grid_layout_utils.h"
24 
25 namespace OHOS::Ace::NG {
GridIrregularFiller(GridLayoutInfo * info,LayoutWrapper * wrapper)26 GridIrregularFiller::GridIrregularFiller(GridLayoutInfo* info, LayoutWrapper* wrapper) : info_(info), wrapper_(wrapper)
27 {}
28 
InitPos(int32_t lineIdx)29 int32_t GridIrregularFiller::InitPos(int32_t lineIdx)
30 {
31     posX_ = -1;
32     posY_ = lineIdx;
33     return info_->FindEndIdx(lineIdx - 1).itemIdx;
34 }
35 
36 using Result = GridIrregularFiller::FillResult;
Fill(const FillParameters & params,float targetLen,int32_t startingLine)37 Result GridIrregularFiller::Fill(const FillParameters& params, float targetLen, int32_t startingLine)
38 {
39     startingLine = std::max(0, startingLine);
40     int32_t idx = InitPos(startingLine);
41     // no gap on first row
42     float len = -params.mainGap;
43     while (idx < info_->childrenCount_ - 1) {
44         int32_t prevRow = posY_;
45         if (!FindNextItem(++idx)) {
46             FillOne(idx);
47         }
48         // (posY_ > prevRow) implies that the previous row has been filled
49 
50         int32_t row = prevRow;
51         if (UpdateLength(len, targetLen, row, posY_, params.mainGap)) {
52             return { len, row, idx - 1 };
53         }
54 
55         MeasureItem(params, idx, posX_, posY_, false);
56     }
57 
58     if (info_->lineHeightMap_.empty()) {
59         return {};
60     }
61     // last child reached
62     int32_t lastRow = info_->lineHeightMap_.rbegin()->first;
63     if (UpdateLength(len, targetLen, posY_, lastRow + 1, params.mainGap)) {
64         return { len, posY_, idx };
65     }
66     return { len, lastRow, idx };
67 }
68 
FillToTarget(const FillParameters & params,int32_t targetIdx,int32_t startingLine)69 void GridIrregularFiller::FillToTarget(const FillParameters& params, int32_t targetIdx, int32_t startingLine)
70 {
71     if (targetIdx >= info_->childrenCount_) {
72         targetIdx = info_->childrenCount_ - 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     while (AdvancePos()) {
148         if (mat.at(posY_).at(posX_) == target) {
149             return true;
150         }
151     }
152     // to handle empty tiles in the middle of matrix, check next row
153     auto nextRow = mat.find(posY_ + 1);
154     while (nextRow != mat.end()) {
155         for (const auto [col, item] : nextRow->second) {
156             if (item == target) {
157                 posY_ = nextRow->first;
158                 posX_ = col;
159                 return true;
160             }
161         }
162         ++nextRow;
163     }
164     return false;
165 }
166 
AdvancePos()167 bool GridIrregularFiller::AdvancePos()
168 {
169     ++posX_;
170     if (posX_ == info_->crossCount_) {
171         // go to next row
172         ++posY_;
173         posX_ = 0;
174     }
175 
176     const auto& mat = info_->gridMatrix_;
177     if (mat.find(posY_) == mat.end()) {
178         return false;
179     }
180 
181     const auto& row = mat.at(posY_);
182     return row.find(posX_) != row.end();
183 }
184 
UpdateLength(float & len,float targetLen,int32_t & row,int32_t rowBound,float mainGap) const185 bool GridIrregularFiller::UpdateLength(float& len, float targetLen, int32_t& row, int32_t rowBound, float mainGap) const
186 {
187     for (; row < rowBound; ++row) {
188         auto lineHeightIt = info_->lineHeightMap_.find(row);
189         if (lineHeightIt == info_->lineHeightMap_.end()) {
190             TAG_LOGW(AceLogTag::ACE_GRID, "line height at row %{public}d not prepared after forward measure", posY_);
191             continue;
192         }
193         len += lineHeightIt->second + mainGap;
194         if (GreatOrEqual(len, targetLen)) {
195             return true;
196         }
197     }
198     return false;
199 }
200 
MeasureItem(const FillParameters & params,int32_t itemIdx,int32_t col,int32_t row,bool isCache)201 std::pair<float, LayoutConstraintF> GridIrregularFiller::MeasureItem(
202     const FillParameters& params, int32_t itemIdx, int32_t col, int32_t row, bool isCache)
203 {
204     auto props = AceType::DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
205     auto constraint = props->CreateChildConstraint();
206     auto child = wrapper_->GetOrCreateChildByIndex(itemIdx, !isCache, isCache);
207     CHECK_NULL_RETURN(child, {});
208 
209     const auto itemSize = GridLayoutUtils::GetItemSize(info_, wrapper_, itemIdx);
210     float crossLen = 0.0f;
211     for (int32_t i = 0; i < itemSize.columns; ++i) {
212         crossLen += params.crossLens[i + col];
213     }
214     crossLen += params.crossGap * (itemSize.columns - 1);
215     constraint.percentReference.SetCrossSize(crossLen, info_->axis_);
216     if (info_->axis_ == Axis::VERTICAL) {
217         constraint.maxSize = SizeF { crossLen, Infinity<float>() };
218         constraint.parentIdealSize = OptionalSizeF(crossLen, std::nullopt);
219     } else {
220         constraint.maxSize = SizeF { Infinity<float>(), crossLen };
221         constraint.parentIdealSize = OptionalSizeF(std::nullopt, crossLen);
222     }
223 
224     child->Measure(constraint);
225     SetItemInfo(itemIdx, row, col, itemSize);
226 
227     float childHeight = child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_);
228     // spread height to each row.
229     float heightPerRow = (childHeight - (params.mainGap * (itemSize.rows - 1))) / itemSize.rows;
230     for (int32_t i = 0; i < itemSize.rows; ++i) {
231         info_->lineHeightMap_[row + i] = std::max(info_->lineHeightMap_[row + i], heightPerRow);
232     }
233     return { childHeight, constraint };
234 }
235 
InitPosToLastItem(int32_t lineIdx)236 int32_t GridIrregularFiller::InitPosToLastItem(int32_t lineIdx)
237 {
238     auto res = info_->FindEndIdx(lineIdx);
239     posX_ = res.x;
240     posY_ = std::max(0, res.y);
241     return res.itemIdx;
242 }
243 
FillMatrixOnly(int32_t targetIdx)244 int32_t GridIrregularFiller::FillMatrixOnly(int32_t targetIdx)
245 {
246     if (targetIdx >= info_->childrenCount_) {
247         targetIdx = info_->childrenCount_ - 1;
248     }
249     int32_t idx = InitPosToLastItem(static_cast<int32_t>(info_->gridMatrix_.size()) - 1);
250     while (idx < targetIdx) {
251         if (!FindNextItem(++idx)) {
252             FillOne(idx);
253         }
254     }
255     return posY_;
256 }
257 
FillMatrixByLine(int32_t startingLine,int32_t targetLine)258 int32_t GridIrregularFiller::FillMatrixByLine(int32_t startingLine, int32_t targetLine)
259 {
260     int32_t idx = InitPosToLastItem(startingLine);
261     while (posY_ < targetLine && idx < info_->childrenCount_ - 1) {
262         if (!FindNextItem(++idx)) {
263             FillOne(idx);
264         }
265     }
266     return idx;
267 }
268 
MeasureBackward(const FillParameters & params,float targetLen,int32_t startingLine)269 float GridIrregularFiller::MeasureBackward(const FillParameters& params, float targetLen, int32_t startingLine)
270 {
271     float len = 0.0f;
272     posY_ = startingLine;
273     std::unordered_set<int32_t> measured;
274 
275     for (; posY_ >= 0 && LessNotEqual(len, targetLen); --posY_) {
276         BackwardImpl(measured, params);
277         auto lineHeightIt = info_->lineHeightMap_.find(posY_);
278         if (lineHeightIt == info_->lineHeightMap_.end()) {
279             TAG_LOGW(AceLogTag::ACE_GRID, "line height at row %{public}d not prepared after backward measure", posY_);
280             continue;
281         }
282         len += params.mainGap + lineHeightIt->second;
283     }
284     return len;
285 }
286 
MeasureBackwardToTarget(const FillParameters & params,int32_t targetLine,int32_t startingLine)287 void GridIrregularFiller::MeasureBackwardToTarget(
288     const FillParameters& params, int32_t targetLine, int32_t startingLine)
289 {
290     if (targetLine < 0) {
291         return;
292     }
293     posY_ = startingLine;
294 
295     std::unordered_set<int32_t> measured;
296     for (; posY_ >= targetLine; --posY_) {
297         BackwardImpl(measured, params);
298     }
299 }
300 
MeasureLineWithIrregulars(const FillParameters & params,const int32_t line)301 void GridIrregularFiller::MeasureLineWithIrregulars(const FillParameters& params, const int32_t line)
302 {
303     if (line == 0) {
304         return;
305     }
306     const auto it = info_->gridMatrix_.find(line);
307     if (it == info_->gridMatrix_.end()) {
308         return;
309     }
310     std::unordered_set<int32_t> visited;
311     int32_t topRow = line;
312     for (const auto& [c, itemIdx] : it->second) {
313         if (itemIdx == 0) {
314             topRow = 0;
315             break;
316         }
317         if (itemIdx < 0 && !visited.count(std::abs(itemIdx))) {
318             topRow = std::min(FindItemTopRow(it->first, c), topRow);
319         }
320         visited.insert(std::abs(itemIdx));
321     }
322     if (topRow < line) {
323         MeasureBackwardToTarget(params, topRow, line);
324     }
325 }
326 
BackwardImpl(std::unordered_set<int32_t> & measured,const FillParameters & params)327 void GridIrregularFiller::BackwardImpl(std::unordered_set<int32_t>& measured, const FillParameters& params)
328 {
329     auto it = info_->gridMatrix_.find(posY_);
330     if (it == info_->gridMatrix_.end()) {
331         TAG_LOGW(AceLogTag::ACE_GRID, "positionY %{public}d not found in matrix in backward measure.", posY_);
332         return;
333     }
334     const auto& row = it->second;
335     for (const auto& colIt : row) {
336         const int32_t& c = colIt.first;
337         const int32_t itemIdx = std::abs(colIt.second);
338         if (measured.count(itemIdx)) {
339             continue;
340         }
341 
342         const int32_t topRow = FindItemTopRow(posY_, c);
343         MeasureItem(params, itemIdx, c, topRow, false);
344         // measure irregular items only once from the bottom-left tile
345         measured.insert(itemIdx);
346     }
347 }
348 
FindItemTopRow(int32_t row,int32_t col) const349 int32_t GridIrregularFiller::FindItemTopRow(int32_t row, int32_t col) const
350 {
351     if (info_->gridMatrix_.at(row).at(col) == 0) {
352         return 0;
353     }
354 
355     while (info_->gridMatrix_.at(row).at(col) < 0) {
356         --row;
357     }
358     return row;
359 }
360 
SetItemInfo(int32_t idx,int32_t row,int32_t col,GridItemSize size)361 void GridIrregularFiller::SetItemInfo(int32_t idx, int32_t row, int32_t col, GridItemSize size)
362 {
363     if (info_->axis_ == Axis::HORIZONTAL) {
364         std::swap(row, col);
365         std::swap(size.rows, size.columns);
366     }
367     auto item = wrapper_->GetChildByIndex(idx);
368     CHECK_NULL_VOID(item);
369     auto pattern = item->GetHostNode()->GetPattern<GridItemPattern>();
370     CHECK_NULL_VOID(pattern);
371     auto props = pattern->GetLayoutProperty<GridItemLayoutProperty>();
372     props->UpdateMainIndex(row);
373     props->UpdateCrossIndex(col);
374 
375     if (size.rows == 1 && size.columns == 1) {
376         return;
377     }
378     pattern->SetIrregularItemInfo({ .mainIndex = row,
379         .crossIndex = col,
380         .mainSpan = size.rows,
381         .crossSpan = size.columns,
382         .mainStart = row,
383         .mainEnd = row + size.rows - 1,
384         .crossStart = col,
385         .crossEnd = col + size.columns - 1 });
386 }
387 } // namespace OHOS::Ace::NG
388