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