1 /*
2  * Copyright (c) 2022 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/grid_layout/grid_layout_algorithm.h"
17 
18 #include <cstdint>
19 
20 #include "base/geometry/ng/offset_t.h"
21 #include "base/geometry/ng/size_t.h"
22 #include "base/utils/utils.h"
23 #include "core/components_ng/layout/layout_wrapper.h"
24 #include "core/components_ng/pattern/grid/grid_item_pattern.h"
25 #include "core/components_ng/pattern/grid/grid_utils.h"
26 #include "core/components_ng/property/measure_utils.h"
27 #include "core/components_ng/property/templates_parser.h"
28 
29 namespace OHOS::Ace::NG {
30 namespace {
OffsetByAlign(const SizeF & size,float rowLen,float colLen,float & positionX,float & positionY)31 void OffsetByAlign(const SizeF& size, float rowLen, float colLen, float& positionX, float& positionY)
32 {
33     // only support Alignment.Center now
34     auto width = size.Width();
35     positionX += (colLen - width) / 2;
36     auto height = size.Height();
37     positionY += (rowLen - height) / 2;
38 }
39 } // namespace
40 
CreateChildConstraint(const SizeF & idealSize,const RefPtr<GridLayoutProperty> & layoutProperty,int32_t row,int32_t col,int32_t & rowSpan,int32_t & colSpan,const RefPtr<LayoutProperty> & childLayoutProperty) const41 LayoutConstraintF GridLayoutAlgorithm::CreateChildConstraint(const SizeF& idealSize,
42     const RefPtr<GridLayoutProperty>& layoutProperty, int32_t row, int32_t col, int32_t& rowSpan, int32_t& colSpan,
43     const RefPtr<LayoutProperty>& childLayoutProperty) const
44 {
45     LayoutConstraintF layoutConstraint = layoutProperty->CreateChildConstraint();
46 
47     float rowLen = 0.0;
48     for (int32_t i = 0; i < rowSpan; ++i) {
49         rowLen += GetItemSize(row + i, col, true);
50     }
51     rowLen += (rowSpan - 1) * rowsGap_;
52 
53     float colLen = 0.0;
54     for (int32_t i = 0; i < colSpan; ++i) {
55         colLen += GetItemSize(row, col + i, false);
56     }
57     colLen += (colSpan - 1) * columnsGap_;
58 
59     layoutConstraint.maxSize = SizeF(colLen, rowLen);
60     layoutConstraint.percentReference = SizeF(colLen, rowLen);
61     if (!childLayoutProperty || !childLayoutProperty->GetCalcLayoutConstraint()) {
62         layoutConstraint.selfIdealSize.UpdateIllegalSizeWithCheck(layoutConstraint.maxSize);
63     }
64     return layoutConstraint;
65 }
66 
InitGridCeils(LayoutWrapper * layoutWrapper,const SizeF & idealSize)67 void GridLayoutAlgorithm::InitGridCeils(LayoutWrapper* layoutWrapper, const SizeF& idealSize)
68 {
69     auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
70     CHECK_NULL_VOID(layoutProperty);
71     auto scale = layoutProperty->GetLayoutConstraint()->scaleProperty;
72     rowsGap_ = ConvertToPx(layoutProperty->GetRowsGap().value_or(0.0_vp), scale, idealSize.Height()).value_or(0);
73     columnsGap_ = ConvertToPx(layoutProperty->GetColumnsGap().value_or(0.0_vp), scale, idealSize.Width()).value_or(0);
74     auto rows = ParseTemplateArgs(GridUtils::ParseArgs(layoutProperty->GetRowsTemplate().value_or("")),
75         idealSize.Height(), rowsGap_, layoutWrapper->GetTotalChildCount());
76     auto cols = ParseTemplateArgs(GridUtils::ParseArgs(layoutProperty->GetColumnsTemplate().value_or("")),
77         idealSize.Width(), columnsGap_, layoutWrapper->GetTotalChildCount());
78     auto rowsLen = rows.first;
79     auto colsLen = cols.first;
80     if (rowsLen.empty()) {
81         rowsLen.push_back(idealSize.Height());
82     }
83     rowsGap_ = rows.second;
84 
85     if (colsLen.empty()) {
86         colsLen.push_back(idealSize.Width());
87     }
88     columnsGap_ = cols.second;
89 
90     if (static_cast<uint32_t>(mainCount_) != rowsLen.size()) {
91         mainCount_ = static_cast<int32_t>(rowsLen.size());
92     }
93     if (static_cast<uint32_t>(crossCount_) != colsLen.size()) {
94         crossCount_ = static_cast<int32_t>(colsLen.size());
95         gridLayoutInfo_.crossCount_ = crossCount_;
96     }
97 
98     gridCells_.clear();
99     int32_t row = 0;
100     for (const auto& height : rowsLen) {
101         int32_t col = 0;
102         for (const auto& width : colsLen) {
103             gridCells_[row][col] = SizeF(width, height);
104             ++col;
105         }
106         ++row;
107     }
108 }
109 
CheckGridPlaced(int32_t index,int32_t row,int32_t col,int32_t & rowSpan,int32_t & colSpan)110 bool GridLayoutAlgorithm::CheckGridPlaced(int32_t index, int32_t row, int32_t col, int32_t& rowSpan, int32_t& colSpan)
111 {
112     auto rowIter = gridLayoutInfo_.gridMatrix_.find(row);
113     if (rowIter != gridLayoutInfo_.gridMatrix_.end()) {
114         auto colIter = rowIter->second.find(col);
115         if (colIter != rowIter->second.end()) {
116             return false;
117         }
118     }
119     rowSpan = std::min(mainCount_ - row, rowSpan);
120     colSpan = std::min(crossCount_ - col, colSpan);
121     int32_t rSpan = 0;
122     int32_t cSpan = 0;
123     int32_t retColSpan = 1;
124     while (rSpan < rowSpan) {
125         rowIter = gridLayoutInfo_.gridMatrix_.find(rSpan + row);
126         if (rowIter != gridLayoutInfo_.gridMatrix_.end()) {
127             cSpan = 0;
128             while (cSpan < colSpan) {
129                 if (rowIter->second.find(cSpan + col) != rowIter->second.end()) {
130                     colSpan = cSpan;
131                     break;
132                 }
133                 ++cSpan;
134             }
135         } else {
136             cSpan = colSpan;
137         }
138         if (retColSpan > cSpan) {
139             break;
140         }
141         retColSpan = cSpan;
142         ++rSpan;
143     }
144 
145     rowSpan = rSpan;
146     colSpan = retColSpan;
147     for (int32_t i = row; i < row + rowSpan; ++i) {
148         std::map<int32_t, int32_t> rowMap;
149         auto iter = gridLayoutInfo_.gridMatrix_.find(i);
150         if (iter != gridLayoutInfo_.gridMatrix_.end()) {
151             rowMap = iter->second;
152         }
153         for (int32_t j = col; j < col + colSpan; ++j) {
154             rowMap.emplace(std::make_pair(j, index));
155         }
156         gridLayoutInfo_.gridMatrix_[i] = rowMap;
157     }
158     return true;
159 }
160 
GetNextGrid(int32_t & curRow,int32_t & curCol) const161 void GridLayoutAlgorithm::GetNextGrid(int32_t& curRow, int32_t& curCol) const
162 {
163     if (isVertical_) {
164         ++curCol;
165         if (curCol >= crossCount_) {
166             curCol = 0;
167             ++curRow;
168         }
169     } else {
170         ++curRow;
171         if (curRow >= mainCount_) {
172             curRow = 0;
173             ++curCol;
174         }
175     }
176 }
177 
ComputeItemPosition(LayoutWrapper * layoutWrapper,int32_t row,int32_t col,int32_t & rowSpan,int32_t & colSpan,const RefPtr<LayoutWrapper> & childLayoutWrapper) const178 OffsetF GridLayoutAlgorithm::ComputeItemPosition(LayoutWrapper* layoutWrapper, int32_t row, int32_t col,
179     int32_t& rowSpan, int32_t& colSpan, const RefPtr<LayoutWrapper>& childLayoutWrapper) const
180 {
181     auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
182     CHECK_NULL_RETURN(layoutProperty, OffsetF());
183     auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
184     MinusPaddingToSize(layoutProperty->CreatePaddingAndBorder(), frameSize);
185 
186     // Calculate the position for current child.
187     float positionX = 0.0f;
188     float positionY = 0.0f;
189     for (int32_t i = 0; i < row; ++i) {
190         positionY += GetItemSize(i, 0, true);
191     }
192     positionY += row * rowsGap_;
193     for (int32_t i = 0; i < col; ++i) {
194         positionX += GetItemSize(0, i, false);
195     }
196     positionX += col * columnsGap_;
197 
198     // Calculate the size for current child.
199     float rowLen = 0.0f;
200     float colLen = 0.0f;
201     for (int32_t i = 0; i < rowSpan; ++i) {
202         rowLen += GetItemSize(row + i, col, true);
203     }
204     rowLen += (rowSpan - 1) * rowsGap_;
205     for (int32_t i = 0; i < colSpan; ++i) {
206         colLen += GetItemSize(row, col + i, false);
207     }
208     colLen += (colSpan - 1) * columnsGap_;
209 
210     if (childLayoutWrapper) {
211         auto childSize = childLayoutWrapper->GetGeometryNode()->GetMarginFrameSize();
212         OffsetByAlign(childSize, rowLen, colLen, positionX, positionY);
213     }
214 
215     // If RTL, place the item from right.
216     if (rightToLeft_) {
217         positionX = frameSize.Width() - positionX - colLen;
218     }
219     return OffsetF(positionX, positionY);
220 }
221 
GetItemSize(int32_t row,int32_t col,bool height) const222 float GridLayoutAlgorithm::GetItemSize(int32_t row, int32_t col, bool height) const
223 {
224     auto nextC = gridCells_.find(row);
225     if (nextC != gridCells_.end()) {
226         auto nextCol = nextC->second;
227         auto nextColRow = nextCol.find(col);
228         if (nextColRow != nextCol.end()) {
229             return height ? nextColRow->second.Height() : nextColRow->second.Width();
230         }
231     }
232     return 0.0;
233 }
234 
GetItemRect(const RefPtr<GridLayoutProperty> & gridLayoutProperty,const RefPtr<GridItemLayoutProperty> & childLayoutProperty,int32_t index) const235 GridItemRect GridLayoutAlgorithm::GetItemRect(const RefPtr<GridLayoutProperty>& gridLayoutProperty,
236     const RefPtr<GridItemLayoutProperty>& childLayoutProperty, int32_t index) const
237 {
238     GridItemRect rect;
239     if (gridLayoutProperty->GetLayoutOptions().has_value()) {
240         const auto& options = *gridLayoutProperty->GetLayoutOptions();
241         if (options.getRectByIndex) {
242             rect = options.getRectByIndex(index);
243             rect.rowSpan = std::max(rect.rowSpan, 1);
244             rect.columnSpan = std::max(rect.columnSpan, 1);
245         }
246     } else {
247         if (childLayoutProperty) {
248             rect.rowStart = childLayoutProperty->GetRowStart().value_or(-1);
249             rect.columnStart = childLayoutProperty->GetColumnStart().value_or(-1);
250             rect.rowSpan = std::max(childLayoutProperty->GetRowEnd().value_or(-1) - rect.rowStart + 1, 1);
251             rect.columnSpan = std::max(childLayoutProperty->GetColumnEnd().value_or(-1) - rect.columnStart + 1, 1);
252         }
253     }
254     return rect;
255 }
256 
Measure(LayoutWrapper * layoutWrapper)257 void GridLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
258 {
259     auto gridLayoutProperty = AceType::DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
260     CHECK_NULL_VOID(gridLayoutProperty);
261     Axis axis = gridLayoutInfo_.axis_;
262     auto idealSize =
263         CreateIdealSize(gridLayoutProperty->GetLayoutConstraint().value(), axis, MeasureType::MATCH_PARENT, true);
264     if (GreatOrEqual(GetMainAxisSize(idealSize, axis), Infinity<float>())) {
265         idealSize = gridLayoutProperty->GetLayoutConstraint().value().percentReference;
266         TAG_LOGI(AceLogTag::ACE_GRID, "size of main axis value is infinity, use percent reference");
267     }
268     rightToLeft_ = layoutWrapper->GetLayoutProperty()->GetNonAutoLayoutDirection() == TextDirection::RTL;
269 
270     layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
271     MinusPaddingToSize(gridLayoutProperty->CreatePaddingAndBorder(), idealSize);
272     InitGridCeils(layoutWrapper, idealSize);
273 
274     int32_t rowIndex = 0;
275     int32_t colIndex = 0;
276     int32_t itemIndex = 0;
277     itemsPosition_.clear();
278     gridLayoutInfo_.gridMatrix_.clear();
279     gridLayoutInfo_.startIndex_ = 0;
280     gridLayoutInfo_.hasBigItem_ = false;
281     for (int32_t index = 0; index < mainCount_ * crossCount_; ++index) {
282         auto childLayoutWrapper = layoutWrapper->GetOrCreateChildByIndex(index);
283         if (!childLayoutWrapper) {
284             break;
285         }
286         auto layoutProperty = childLayoutWrapper->GetLayoutProperty();
287         if (!layoutProperty) {
288             break;
289         }
290 
291         auto childLayoutProperty = DynamicCast<GridItemLayoutProperty>(layoutProperty);
292         auto rect = GetItemRect(gridLayoutProperty, childLayoutProperty, index);
293         int32_t itemRowStart = rect.rowStart;
294         int32_t itemColStart = rect.columnStart;
295         int32_t itemRowSpan = rect.rowSpan;
296         int32_t itemColSpan = rect.columnSpan;
297         if (itemRowSpan > 1 || itemColSpan > 1) {
298             gridLayoutInfo_.hasBigItem_ = true;
299         }
300 
301         if (itemRowStart >= 0 && itemRowStart < mainCount_ && itemColStart >= 0 && itemColStart < crossCount_ &&
302             CheckGridPlaced(itemIndex, itemRowStart, itemColStart, itemRowSpan, itemColSpan)) {
303             childLayoutWrapper->Measure(CreateChildConstraint(idealSize, gridLayoutProperty, itemRowStart, itemColStart,
304                 itemRowSpan, itemColSpan, childLayoutProperty));
305             itemsPosition_.try_emplace(index, ComputeItemPosition(layoutWrapper, itemRowStart, itemColStart,
306                 itemRowSpan, itemColSpan, childLayoutWrapper));
307         } else {
308             while (!CheckGridPlaced(itemIndex, rowIndex, colIndex, itemRowSpan, itemColSpan)) {
309                 GetNextGrid(rowIndex, colIndex);
310                 if (rowIndex >= mainCount_ || colIndex >= crossCount_) {
311                     break;
312                 }
313             }
314             if (rowIndex >= mainCount_ || colIndex >= crossCount_) {
315                 continue;
316             }
317             childLayoutWrapper->Measure(CreateChildConstraint(
318                 idealSize, gridLayoutProperty, rowIndex, colIndex, itemRowSpan, itemColSpan, childLayoutProperty));
319             itemsPosition_.try_emplace(index,
320                 ComputeItemPosition(layoutWrapper, rowIndex, colIndex, itemRowSpan, itemColSpan, childLayoutWrapper));
321 
322             PrintConflictingPositionLog(itemIndex, rect, rowIndex, colIndex, itemRowSpan, itemColSpan);
323         }
324 
325         if (childLayoutProperty) {
326             childLayoutProperty->UpdateRealRowSpan(itemRowSpan);
327             childLayoutProperty->UpdateRealColumnSpan(itemColSpan);
328         }
329 
330         ++itemIndex;
331     }
332     gridLayoutInfo_.endIndex_ = itemIndex - 1;
333     gridLayoutInfo_.startMainLineIndex_ = 0;
334     gridLayoutInfo_.endMainLineIndex_ = static_cast<int32_t>(gridLayoutInfo_.gridMatrix_.size()) - 1 ;
335 }
336 
Layout(LayoutWrapper * layoutWrapper)337 void GridLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
338 {
339     CHECK_NULL_VOID(layoutWrapper);
340     auto layoutProperty = AceType::DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
341     CHECK_NULL_VOID(layoutProperty);
342     auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
343     auto padding = layoutProperty->CreatePaddingAndBorder();
344     MinusPaddingToSize(padding, frameSize);
345     layoutWrapper->RemoveAllChildInRenderTree();
346     for (int32_t index = 0; index < mainCount_ * crossCount_; ++index) {
347         OffsetF childOffset;
348         auto childPosition = itemsPosition_.find(index);
349         if (childPosition != itemsPosition_.end()) {
350             auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(index);
351             if (!childWrapper) {
352                 break;
353             }
354             childOffset = childPosition->second;
355             childWrapper->GetGeometryNode()->SetMarginFrameOffset(padding.Offset() + childOffset);
356             childWrapper->Layout();
357         }
358     }
359 
360     for (const auto& mainLine : gridLayoutInfo_.gridMatrix_) {
361         int32_t itemIndex = -1;
362         for (const auto& crossLine : mainLine.second) {
363             // If item index is the same, must be the same GridItem, needn't layout again.
364             if (itemIndex == crossLine.second) {
365                 continue;
366             }
367             itemIndex = crossLine.second;
368             auto wrapper = layoutWrapper->GetOrCreateChildByIndex(itemIndex);
369             if (!wrapper) {
370                 break;
371             }
372             auto layoutProperty = wrapper->GetLayoutProperty();
373             CHECK_NULL_VOID(layoutProperty);
374             auto gridItemLayoutProperty = AceType::DynamicCast<GridItemLayoutProperty>(layoutProperty);
375             CHECK_NULL_VOID(gridItemLayoutProperty);
376             gridItemLayoutProperty->UpdateMainIndex(mainLine.first);
377             gridItemLayoutProperty->UpdateCrossIndex(crossLine.first);
378             UpdateRealGridItemPositionInfo(wrapper, mainLine.first, crossLine.first);
379         }
380     }
381 }
382 
PrintConflictingPositionLog(int32_t itemIndex,GridItemRect rect,int32_t rowIndex,int32_t colIndex,int32_t rowSpan,int32_t colSpan)383 void GridLayoutAlgorithm::PrintConflictingPositionLog(
384     int32_t itemIndex, GridItemRect rect, int32_t rowIndex, int32_t colIndex, int32_t rowSpan, int32_t colSpan)
385 {
386     if (!(rect.rowStart == -1 && rect.columnStart == -1 && rect.rowSpan == 1 && rect.columnSpan == 1)) {
387         TAG_LOGI(AceLogTag::ACE_GRID,
388             "item index:%{public}d has been set to a conflicting position row:%{public}d, col:%{public}d, "
389             "rowSpan:%{public}d, colSpan:%{public}d. reset to [%{public}d, %{public}d, %{public}d, %{public}d]",
390             itemIndex, rect.rowStart, rect.columnStart, rect.rowSpan, rect.columnSpan, rowIndex, colIndex, rowSpan,
391             colSpan);
392     }
393 }
394 } // namespace OHOS::Ace::NG
395