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