1 /*
2 * Copyright (c) 2023 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_layout_range_solver.h"
17
18 #include "core/components_ng/pattern/grid/grid_layout_property.h"
19 #include "core/components_ng/pattern/grid/irregular/grid_layout_utils.h"
20
21 namespace OHOS::Ace::NG {
GridLayoutRangeSolver(GridLayoutInfo * info,LayoutWrapper * wrapper)22 GridLayoutRangeSolver::GridLayoutRangeSolver(GridLayoutInfo* info, LayoutWrapper* wrapper)
23 : info_(info), wrapper_(wrapper)
24 {
25 auto props = AceType::DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
26 opts_ = &props->GetLayoutOptions().value();
27 };
28
29 using Result = GridLayoutRangeSolver::StartingRowInfo;
FindStartingRow(float mainGap)30 Result GridLayoutRangeSolver::FindStartingRow(float mainGap)
31 {
32 if (info_->gridMatrix_.empty() || info_->lineHeightMap_.empty()) {
33 return { 0, 0, 0.0f };
34 }
35 if (NearZero(info_->currentOffset_)) {
36 return { info_->startMainLineIndex_, info_->startIndex_, 0.0f };
37 }
38 if (Negative(info_->currentOffset_)) {
39 return SolveForward(mainGap, -info_->currentOffset_, info_->startMainLineIndex_);
40 }
41 return SolveBackward(mainGap, info_->currentOffset_, info_->startMainLineIndex_);
42 }
43
44 using RangeInfo = GridLayoutRangeSolver::RangeInfo;
FindRangeOnJump(int32_t jumpIdx,int32_t jumpLineIdx,float mainGap)45 RangeInfo GridLayoutRangeSolver::FindRangeOnJump(int32_t jumpIdx, int32_t jumpLineIdx, float mainGap)
46 {
47 auto mainSize = wrapper_->GetGeometryNode()->GetContentSize().MainSize(info_->axis_);
48 /*
49 Notice that finding the first line in ScrollAlign::END is the same as having the jumpLine matching the top of the
50 viewport and applying a positive whole-page offset, so we can directly use SolveBackward. But for
51 ScrollAlign::START, we have to change SolveForward a bit to find the ending row.
52 */
53 switch (info_->scrollAlign_) {
54 case ScrollAlign::START: {
55 auto [startRow, startIdx] = CheckMultiRow(jumpLineIdx);
56 float offset = -info_->GetHeightInRange(startRow, jumpLineIdx, mainGap);
57 auto [endLineIdx, endIdx] = SolveForwardForEndIdx(mainGap, mainSize, jumpLineIdx);
58 return { startRow, startIdx, offset, endLineIdx, endIdx };
59 }
60 case ScrollAlign::CENTER: {
61 // align by item center
62 auto size = GridLayoutUtils::GetItemSize(info_, wrapper_, jumpIdx);
63 const auto [centerLine, offset] = info_->FindItemCenter(jumpLineIdx, size.rows, mainGap);
64 const float halfMainSize = mainSize / 2.0f;
65 auto [endLineIdx, endIdx] = SolveForwardForEndIdx(mainGap, halfMainSize + offset, centerLine);
66 auto res = SolveBackward(mainGap, halfMainSize - offset, centerLine);
67 return { res.row, res.idx, res.pos, endLineIdx, endIdx };
68 }
69 case ScrollAlign::END: {
70 auto it = info_->lineHeightMap_.find(jumpLineIdx);
71 if (it == info_->lineHeightMap_.end()) {
72 TAG_LOGW(AceLogTag::ACE_GRID, "line height at %{public}d not prepared during jump", jumpLineIdx);
73 return {};
74 }
75 auto res = SolveBackward(mainGap, mainSize - it->second, jumpLineIdx);
76 return { res.row, res.idx, res.pos, jumpLineIdx, info_->FindEndIdx(jumpLineIdx).itemIdx };
77 }
78 default:
79 return {};
80 }
81 }
82
SolveForward(float mainGap,float targetLen,const int32_t idx)83 Result GridLayoutRangeSolver::SolveForward(float mainGap, float targetLen, const int32_t idx)
84 {
85 float len = -mainGap;
86 auto it = info_->lineHeightMap_.find(idx);
87 for (; it != info_->lineHeightMap_.end(); ++it) {
88 if (GreatNotEqual(len + it->second + mainGap, targetLen)) {
89 break;
90 }
91 len += it->second + mainGap;
92 }
93 if (it == info_->lineHeightMap_.end()) {
94 len -= (--it)->second + mainGap;
95 }
96 auto [startRow, startIdx] = CheckMultiRow(it->first);
97 for (int32_t i = it->first; i > startRow; --i) {
98 --it;
99 len -= it->second + mainGap;
100 }
101 return { startRow, startIdx, len - targetLen + mainGap };
102 }
103
SolveForwardForEndIdx(float mainGap,float targetLen,int32_t line)104 std::pair<int32_t, int32_t> GridLayoutRangeSolver::SolveForwardForEndIdx(float mainGap, float targetLen, int32_t line)
105 {
106 if (Negative(targetLen)) {
107 return { -1, -1 };
108 }
109 float len = 0.0f;
110 auto it = info_->lineHeightMap_.find(line);
111 if (it == info_->lineHeightMap_.end()) {
112 return { -1, -1 };
113 }
114
115 for (; LessNotEqual(len, targetLen) && it != info_->lineHeightMap_.end(); ++it) {
116 len += it->second + mainGap;
117 }
118 --it;
119 return { it->first, info_->FindEndIdx(it->first).itemIdx };
120 }
121
SolveBackward(float mainGap,float targetLen,int32_t idx)122 Result GridLayoutRangeSolver::SolveBackward(float mainGap, float targetLen, int32_t idx)
123 {
124 float len = mainGap;
125 while (idx > 0 && LessNotEqual(len, targetLen)) {
126 auto it = info_->lineHeightMap_.find(--idx);
127 if (it == info_->lineHeightMap_.end()) {
128 return { 0, 0, 0.0f };
129 }
130 len += it->second + mainGap;
131 }
132
133 auto [startLine, startItem] = CheckMultiRow(idx);
134 float newOffset = targetLen - len + mainGap;
135 newOffset -= info_->GetHeightInRange(startLine, idx, mainGap);
136 return { startLine, startItem, newOffset };
137 }
138
CheckMultiRow(const int32_t idx)139 std::pair<int32_t, int32_t> GridLayoutRangeSolver::CheckMultiRow(const int32_t idx)
140 {
141 // check multi-row item that occupies Row [idx]
142 auto it = info_->gridMatrix_.find(idx);
143 if (it == info_->gridMatrix_.end()) {
144 return { -1, -1 };
145 }
146
147 const auto& row = it->second;
148 if (row.empty()) {
149 return { -1, -1 };
150 }
151 int32_t startLine = idx;
152 int32_t startItem = row.begin()->second;
153 for (int32_t c = 0; c < info_->crossCount_; ++c) {
154 auto it = row.find(c);
155 if (it == row.end()) {
156 continue;
157 }
158
159 int32_t r = idx;
160 if (it->second == 0) {
161 // Item 0 always start at [0, 0]
162 return { 0, 0 };
163 }
164 if (it->second < 0) {
165 // traverse upwards to find the first row occupied by this item
166 while (r > 0 && info_->gridMatrix_.at(r).at(c) < 0) {
167 --r;
168 }
169 if (r < startLine) {
170 startLine = r;
171 startItem = -it->second;
172 }
173 }
174
175 // skip the columns occupied by this item
176 const int32_t itemIdx = std::abs(it->second);
177 if (opts_->irregularIndexes.find(itemIdx) != opts_->irregularIndexes.end()) {
178 if (opts_->getSizeByIndex) {
179 auto size = opts_->getSizeByIndex(itemIdx);
180 c += (info_->axis_ == Axis::VERTICAL ? size.columns : size.rows) - 1;
181 } else {
182 // no getSizeByIndex implies itemWidth == crossCount
183 break;
184 }
185 }
186 }
187 return { startLine, startItem };
188 }
189 } // namespace OHOS::Ace::NG
190