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