1 /*
2 * Copyright (c) 2022-2025 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/tabs/tabs_layout_algorithm.h"
17
18 #include "core/components_ng/base/frame_node.h"
19 #include "core/components_ng/property/measure_utils.h"
20
21 namespace OHOS::Ace::NG {
22 namespace {
23 constexpr int32_t SWIPER_INDEX = 0;
24 constexpr int32_t DIVIDER_INDEX = 1;
25 constexpr int32_t TAB_BAR_INDEX = 2;
26 constexpr int32_t EFFECT_INDEX = 3;
27 } // namespace
28
Measure(LayoutWrapper * layoutWrapper)29 void TabsLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
30 {
31 auto layoutProperty = AceType::DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
32 CHECK_NULL_VOID(layoutProperty);
33 auto geometryNode = layoutWrapper->GetGeometryNode();
34 CHECK_NULL_VOID(geometryNode);
35 auto axis = layoutProperty->GetAxis().value_or(Axis::HORIZONTAL);
36 auto constraint = layoutProperty->GetLayoutConstraint();
37 auto tabsIdealSize = CreateIdealSizeByPercentRef(constraint.value(), Axis::HORIZONTAL,
38 layoutProperty->GetMeasureType(MeasureType::MATCH_PARENT));
39 auto layoutPolicy = layoutProperty->GetLayoutPolicyProperty();
40 if (layoutPolicy.has_value()) {
41 auto widthLayoutPolicy = layoutPolicy.value().widthLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
42 auto heightLayoutPolicy = layoutPolicy.value().heightLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
43
44 // When the width or height parameter is MATCH_PARENT, set its value to the parent's value.
45 auto layoutPolicySize = ConstrainIdealSizeByLayoutPolicy(constraint.value(),
46 widthLayoutPolicy, heightLayoutPolicy, axis);
47 tabsIdealSize.UpdateIllegalSizeWithCheck(layoutPolicySize);
48 }
49 auto idealSize = tabsIdealSize.ConvertToSizeT();
50 if (GreaterOrEqualToInfinity(idealSize.Width()) || GreaterOrEqualToInfinity(idealSize.Height())) {
51 geometryNode->SetFrameSize(SizeF());
52 return;
53 }
54 geometryNode->SetFrameSize(idealSize);
55 MinusPaddingToSize(layoutProperty->CreatePaddingAndBorder(), idealSize);
56 auto childLayoutConstraint = layoutProperty->CreateChildConstraint();
57 childLayoutConstraint.parentIdealSize = OptionalSizeF(idealSize);
58 float dividerStrokeWidth = 0.0f;
59
60 // Measure tab bar.
61 auto tabBarWrapper = layoutWrapper->GetOrCreateChildByIndex(TAB_BAR_INDEX);
62 SizeF tabBarSize;
63 if (tabBarWrapper) {
64 tabBarWrapper->Measure(childLayoutConstraint);
65 tabBarSize = tabBarWrapper->GetGeometryNode()->GetMarginFrameSize();
66 }
67
68 // Measure effect node.
69 auto effectWrapper = layoutWrapper->GetOrCreateChildByIndex(EFFECT_INDEX);
70 if (effectWrapper) {
71 MeasureEffectNode(layoutProperty, effectWrapper, idealSize);
72 }
73
74 // Measure divider.
75 auto dividerWrapper = layoutWrapper->GetOrCreateChildByIndex(DIVIDER_INDEX);
76 if (dividerWrapper) {
77 dividerStrokeWidth = MeasureDivider(layoutProperty, dividerWrapper, idealSize);
78 }
79
80 auto barOverlap = layoutProperty->GetBarOverlap().value_or(false);
81
82 // Measure swiper.
83 auto swiperWrapper = layoutWrapper->GetOrCreateChildByIndex(SWIPER_INDEX);
84 if (swiperWrapper) {
85 swiperWrapper->GetLayoutProperty()->UpdateLayoutDirection(layoutProperty->GetNonAutoLayoutDirection());
86 }
87 SizeF swiperSize;
88 if (swiperWrapper && swiperWrapper->GetHostNode() && swiperWrapper->GetHostNode()->TotalChildCount() > 0) {
89 swiperSize = MeasureSwiper(layoutProperty, swiperWrapper, idealSize, tabBarSize, dividerStrokeWidth);
90 } else if (swiperWrapper && swiperWrapper->GetGeometryNode()) {
91 swiperWrapper->GetGeometryNode()->SetFrameSize(SizeF());
92 }
93
94 auto paddingH = layoutProperty->CreatePaddingAndBorder().Width();
95 auto paddingV = layoutProperty->CreatePaddingAndBorder().Height();
96 if ((axis == Axis::VERTICAL) && layoutProperty->GetWidthAutoValue(false)) {
97 if (!barOverlap) {
98 geometryNode->SetFrameWidth(tabBarSize.Width() + dividerStrokeWidth + swiperSize.Width() + paddingH);
99 } else {
100 geometryNode->SetFrameWidth(dividerStrokeWidth + swiperSize.Width() + paddingH);
101 }
102 } else if ((axis == Axis::HORIZONTAL) && layoutProperty->GetHeightAutoValue(false)) {
103 if (!barOverlap) {
104 geometryNode->SetFrameHeight(tabBarSize.Height() + dividerStrokeWidth + swiperSize.Height() + paddingV);
105 } else {
106 geometryNode->SetFrameHeight(dividerStrokeWidth + swiperSize.Height() + paddingV);
107 }
108 }
109 }
110
Layout(LayoutWrapper * layoutWrapper)111 void TabsLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
112 {
113 CHECK_NULL_VOID(layoutWrapper);
114 auto geometryNode = layoutWrapper->GetGeometryNode();
115 CHECK_NULL_VOID(geometryNode);
116 auto frameSize = geometryNode->GetFrameSize();
117
118 auto layoutProperty = AceType::DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
119 CHECK_NULL_VOID(layoutProperty);
120 auto tabBarWrapper = layoutWrapper->GetChildByIndex(TAB_BAR_INDEX);
121 auto dividerWrapper = layoutWrapper->GetOrCreateChildByIndex(DIVIDER_INDEX);
122 auto effectWrapper = layoutWrapper->GetChildByIndex(EFFECT_INDEX);
123 auto swiperWrapper = layoutWrapper->GetOrCreateChildByIndex(SWIPER_INDEX);
124 if (!tabBarWrapper || !dividerWrapper || !swiperWrapper) {
125 return;
126 }
127
128 std::vector<OffsetF> offsetList = { OffsetF(), OffsetF(), OffsetF(), OffsetF() };
129 if (frameSize.IsPositive()) {
130 MinusPaddingToSize(layoutProperty->CreatePaddingAndBorder(), frameSize);
131 offsetList = LayoutOffsetList(layoutWrapper, tabBarWrapper, effectWrapper, frameSize);
132 }
133 if (layoutProperty->GetNonAutoLayoutDirection() == TextDirection::RTL) {
134 auto tabsWidth = geometryNode->GetFrameSize().Width();
135 auto swiperWidth = swiperWrapper->GetGeometryNode()->GetFrameSize().Width();
136 auto& swiperOffset = offsetList[SWIPER_INDEX];
137 swiperOffset = OffsetF((tabsWidth - swiperOffset.GetX() - swiperWidth), swiperOffset.GetY());
138 auto dividerWidth = dividerWrapper->GetGeometryNode()->GetFrameSize().Width();
139 auto& dividerOffset = offsetList[DIVIDER_INDEX];
140 dividerOffset = OffsetF((tabsWidth - dividerOffset.GetX() - dividerWidth), dividerOffset.GetY());
141 if (effectWrapper) {
142 auto effectWidth = effectWrapper->GetGeometryNode()->GetMarginFrameSize().Width();
143 auto& effectOffset = offsetList[EFFECT_INDEX];
144 effectOffset = OffsetF((tabsWidth - effectOffset.GetX() - effectWidth), effectOffset.GetY());
145 }
146 auto tabBarWidth = tabBarWrapper->GetGeometryNode()->GetMarginFrameSize().Width();
147 auto& tabBarOffset = offsetList[TAB_BAR_INDEX];
148 tabBarOffset = OffsetF((tabsWidth - tabBarOffset.GetX() - tabBarWidth), tabBarOffset.GetY());
149 }
150 swiperWrapper->GetGeometryNode()->SetMarginFrameOffset(offsetList[SWIPER_INDEX]);
151 swiperWrapper->Layout();
152
153 dividerWrapper->GetGeometryNode()->SetMarginFrameOffset(offsetList[DIVIDER_INDEX]);
154 dividerWrapper->Layout();
155
156 if (effectWrapper) {
157 effectWrapper->GetGeometryNode()->SetMarginFrameOffset(offsetList[EFFECT_INDEX]);
158 effectWrapper->Layout();
159 }
160
161 tabBarWrapper->GetGeometryNode()->SetMarginFrameOffset(offsetList[TAB_BAR_INDEX]);
162 tabBarWrapper->Layout();
163 }
164
LayoutOffsetList(LayoutWrapper * layoutWrapper,const RefPtr<LayoutWrapper> & tabBarWrapper,const RefPtr<LayoutWrapper> & effectNodeWrapper,const SizeF & frameSize) const165 std::vector<OffsetF> TabsLayoutAlgorithm::LayoutOffsetList(
166 LayoutWrapper* layoutWrapper, const RefPtr<LayoutWrapper>& tabBarWrapper,
167 const RefPtr<LayoutWrapper>& effectNodeWrapper, const SizeF& frameSize) const
168 {
169 std::vector<OffsetF> offsetList;
170 OffsetF tabBarOffset;
171 OffsetF dividerOffset;
172 OffsetF swiperOffset;
173 OffsetF effectOffset;
174 auto axis = GetAxis(layoutWrapper);
175 auto barPosition = GetBarPosition(layoutWrapper);
176 auto divider = GetDivider(layoutWrapper);
177 auto tabBarGeometryNode = tabBarWrapper->GetGeometryNode();
178 auto effectNodeGeometryNode = effectNodeWrapper ? effectNodeWrapper->GetGeometryNode() : RefPtr<GeometryNode>();
179 CHECK_NULL_RETURN(tabBarGeometryNode, offsetList);
180 auto tabBarFrameSize = tabBarGeometryNode->GetMarginFrameSize();
181 auto effectNodeFrameSize = effectNodeGeometryNode ? effectNodeGeometryNode->GetMarginFrameSize() : SizeF();
182 auto dividerStrokeWidth = divider.isNull ? 0.0f : divider.strokeWidth.ConvertToPx();
183 auto dividerStartMargin = divider.startMargin.ConvertToPx();
184 auto layoutProperty = DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
185 CHECK_NULL_RETURN(layoutProperty, offsetList);
186 auto paddingOffset = layoutProperty->CreatePaddingAndBorder().Offset();
187 auto barOverlap = layoutProperty->GetBarOverlap().value_or(false);
188
189 if (axis == Axis::HORIZONTAL) {
190 float barPosX = (frameSize.MainSize(axis) - tabBarFrameSize.MainSize(axis)) / 2 + paddingOffset.GetX();
191 float effectPosX = (frameSize.MainSize(axis) - effectNodeFrameSize.MainSize(axis)) / 2 + paddingOffset.GetX();
192 if (barPosition == BarPosition::START) {
193 tabBarOffset = OffsetF(barPosX, paddingOffset.GetY());
194 effectOffset = OffsetF(effectPosX, paddingOffset.GetY());
195 dividerOffset = OffsetF(dividerStartMargin + paddingOffset.GetX(),
196 tabBarFrameSize.CrossSize(axis) + paddingOffset.GetY());
197 swiperOffset = barOverlap ? paddingOffset : OffsetF(paddingOffset.GetX(),
198 tabBarFrameSize.CrossSize(axis) + dividerStrokeWidth + paddingOffset.GetY());
199 } else {
200 tabBarOffset = OffsetF(barPosX, frameSize.CrossSize(axis) -
201 tabBarFrameSize.CrossSize(axis) + paddingOffset.GetY());
202 effectOffset = OffsetF(effectPosX, frameSize.CrossSize(axis) -
203 effectNodeFrameSize.CrossSize(axis) + paddingOffset.GetY());
204 dividerOffset = OffsetF(dividerStartMargin + paddingOffset.GetX(), frameSize.CrossSize(axis) -
205 tabBarFrameSize.CrossSize(axis) + paddingOffset.GetY() - dividerStrokeWidth);
206 swiperOffset = paddingOffset;
207 }
208 } else {
209 float barPosY = (frameSize.MainSize(axis) - tabBarFrameSize.MainSize(axis)) / 2 + paddingOffset.GetY();
210 float effectPosY = (frameSize.MainSize(axis) - effectNodeFrameSize.MainSize(axis)) / 2 + paddingOffset.GetY();
211 if (barPosition == BarPosition::START) {
212 tabBarOffset = OffsetF(paddingOffset.GetX(), barPosY);
213 effectOffset = OffsetF(paddingOffset.GetX(), effectPosY);
214 dividerOffset = OffsetF(tabBarFrameSize.CrossSize(axis) + paddingOffset.GetX(),
215 dividerStartMargin + paddingOffset.GetY());
216 swiperOffset = barOverlap ? paddingOffset : OffsetF(tabBarFrameSize.CrossSize(axis) +
217 paddingOffset.GetX() + dividerStrokeWidth, paddingOffset.GetY());
218 } else {
219 tabBarOffset = OffsetF(frameSize.CrossSize(axis) - tabBarFrameSize.CrossSize(axis) +
220 paddingOffset.GetX(), barPosY);
221 effectOffset = OffsetF(frameSize.CrossSize(axis) - effectNodeFrameSize.CrossSize(axis) +
222 paddingOffset.GetX(), effectPosY);
223 dividerOffset = OffsetF(frameSize.CrossSize(axis) - tabBarFrameSize.CrossSize(axis) +
224 paddingOffset.GetX() - dividerStrokeWidth, dividerStartMargin + paddingOffset.GetY());
225 swiperOffset = paddingOffset;
226 }
227 }
228 offsetList.emplace_back(swiperOffset);
229 offsetList.emplace_back(dividerOffset);
230 offsetList.emplace_back(tabBarOffset);
231 offsetList.emplace_back(effectOffset);
232 return offsetList;
233 }
234
GetBarPosition(LayoutWrapper * layoutWrapper) const235 BarPosition TabsLayoutAlgorithm::GetBarPosition(LayoutWrapper* layoutWrapper) const
236 {
237 auto layoutProperty = AceType::DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
238 CHECK_NULL_RETURN(layoutProperty, BarPosition::START);
239 return layoutProperty->GetTabBarPosition().value_or(BarPosition::START);
240 }
241
GetAxis(LayoutWrapper * layoutWrapper) const242 Axis TabsLayoutAlgorithm::GetAxis(LayoutWrapper* layoutWrapper) const
243 {
244 auto layoutProperty = AceType::DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
245 CHECK_NULL_RETURN(layoutProperty, Axis::HORIZONTAL);
246 return layoutProperty->GetAxis().value_or(Axis::HORIZONTAL);
247 }
248
GetDivider(LayoutWrapper * layoutWrapper) const249 TabsItemDivider TabsLayoutAlgorithm::GetDivider(LayoutWrapper* layoutWrapper) const
250 {
251 auto layoutProperty = AceType::DynamicCast<TabsLayoutProperty>(layoutWrapper->GetLayoutProperty());
252 TabsItemDivider divider;
253 CHECK_NULL_RETURN(layoutProperty, divider);
254 return layoutProperty->GetDivider().value_or(divider);
255 }
256
MeasureDivider(const RefPtr<TabsLayoutProperty> & layoutProperty,const RefPtr<LayoutWrapper> & dividerWrapper,const SizeF & idealSize)257 float TabsLayoutAlgorithm::MeasureDivider(const RefPtr<TabsLayoutProperty>& layoutProperty,
258 const RefPtr<LayoutWrapper>& dividerWrapper, const SizeF& idealSize)
259 {
260 auto constraint = layoutProperty->GetLayoutConstraint();
261
262 auto dividerIdealSize = CreateIdealSize(
263 constraint.value(), Axis::HORIZONTAL, layoutProperty->GetMeasureType(MeasureType::MATCH_PARENT), true);
264 TabsItemDivider defaultDivider;
265 auto axis = layoutProperty->GetAxis().value_or(Axis::HORIZONTAL);
266 auto parentWidth = idealSize.Width();
267 auto parentHeight = idealSize.Height();
268 auto divider = layoutProperty->GetDivider().value_or(defaultDivider);
269 auto dividerStrokeWidth = divider.strokeWidth.ConvertToPx();
270 auto dividerStartMargin = divider.startMargin.ConvertToPx();
271 auto dividerEndMargin = divider.endMargin.ConvertToPx();
272
273 if (axis == Axis::VERTICAL) {
274 dividerIdealSize.SetWidth(dividerStrokeWidth);
275 dividerIdealSize.SetHeight(parentHeight - dividerStartMargin - dividerEndMargin);
276 } else if (axis == Axis::HORIZONTAL) {
277 dividerIdealSize.SetWidth(parentWidth - dividerStartMargin - dividerEndMargin);
278 dividerIdealSize.SetHeight(dividerStrokeWidth);
279 }
280
281 auto dividerLayoutConstraint = layoutProperty->CreateChildConstraint();
282 dividerLayoutConstraint.selfIdealSize = OptionalSizeF(dividerIdealSize);
283 dividerWrapper->Measure(dividerLayoutConstraint);
284
285 return divider.isNull ? 0.0f : dividerStrokeWidth;
286 }
287
MeasureEffectNode(const RefPtr<TabsLayoutProperty> & layoutProperty,const RefPtr<LayoutWrapper> & effectNodeWrapper,const SizeF & idealSize)288 void TabsLayoutAlgorithm::MeasureEffectNode(const RefPtr<TabsLayoutProperty>& layoutProperty,
289 const RefPtr<LayoutWrapper>& effectNodeWrapper, const SizeF& idealSize)
290 {
291 auto constraint = layoutProperty->GetLayoutConstraint();
292
293 auto effectNodeIdealSize = CreateIdealSize(
294 constraint.value(), Axis::HORIZONTAL, layoutProperty->GetMeasureType(MeasureType::MATCH_PARENT), true);
295 TabsEffectNodeOption defaultEffectNodeOption;
296 auto axis = layoutProperty->GetAxis().value_or(Axis::HORIZONTAL);
297 auto parentWidth = idealSize.Width();
298 auto parentHeight = idealSize.Height();
299 auto effectNodeOption = layoutProperty->GetEffectNodeOption().value_or(defaultEffectNodeOption);
300 auto effectNodeWidth = effectNodeOption.strokeWidth.ConvertToPx();
301
302 if (axis == Axis::VERTICAL) {
303 effectNodeIdealSize.SetWidth(effectNodeWidth);
304 effectNodeIdealSize.SetHeight(parentHeight);
305 } else if (axis == Axis::HORIZONTAL) {
306 effectNodeIdealSize.SetWidth(parentWidth);
307 effectNodeIdealSize.SetHeight(effectNodeWidth);
308 }
309
310 auto effectNodeLayoutConstraint = layoutProperty->CreateChildConstraint();
311 effectNodeLayoutConstraint.selfIdealSize = OptionalSizeF(effectNodeIdealSize);
312 effectNodeWrapper->Measure(effectNodeLayoutConstraint);
313 }
314
MeasureSwiper(const RefPtr<TabsLayoutProperty> & layoutProperty,RefPtr<LayoutWrapper> & swiperWrapper,const SizeF & idealSize,const SizeF & tabBarSize,const float dividerWidth)315 SizeF TabsLayoutAlgorithm::MeasureSwiper(const RefPtr<TabsLayoutProperty>& layoutProperty,
316 RefPtr<LayoutWrapper>& swiperWrapper, const SizeF& idealSize, const SizeF& tabBarSize, const float dividerWidth)
317 {
318 auto axis = layoutProperty->GetAxis().value_or(Axis::HORIZONTAL);
319 auto barOverlap = layoutProperty->GetBarOverlap().value_or(false);
320 bool autoWidth = layoutProperty->GetWidthAutoValue(false);
321 bool autoHeight = layoutProperty->GetHeightAutoValue(false);
322 SizeF parentIdealSize = idealSize;
323 auto childLayoutConstraint = layoutProperty->CreateChildConstraint();
324 childLayoutConstraint.parentIdealSize = OptionalSizeF(idealSize);
325
326 if (axis == Axis::HORIZONTAL) {
327 if (!barOverlap) {
328 if (!autoHeight) {
329 childLayoutConstraint.selfIdealSize.SetHeight(
330 idealSize.Height() - tabBarSize.Height() - dividerWidth);
331 }
332 childLayoutConstraint.selfIdealSize.SetWidth(idealSize.Width());
333 parentIdealSize.SetHeight(idealSize.Height() - tabBarSize.Height() - dividerWidth);
334 } else {
335 if (!autoHeight) {
336 childLayoutConstraint.selfIdealSize.SetHeight(idealSize.Height());
337 }
338 childLayoutConstraint.selfIdealSize.SetWidth(idealSize.Width());
339 parentIdealSize.SetHeight(idealSize.Height());
340 }
341 } else if (axis == Axis::VERTICAL) {
342 if (!barOverlap) {
343 if (!autoWidth) {
344 childLayoutConstraint.selfIdealSize.SetWidth(
345 idealSize.Width() - tabBarSize.Width() - dividerWidth);
346 }
347 childLayoutConstraint.selfIdealSize.SetHeight(idealSize.Height());
348 parentIdealSize.SetWidth(idealSize.Width() - tabBarSize.Width() - dividerWidth);
349 } else {
350 if (!autoWidth) {
351 childLayoutConstraint.selfIdealSize.SetWidth(idealSize.Width());
352 }
353 childLayoutConstraint.selfIdealSize.SetHeight(idealSize.Height());
354 parentIdealSize.SetWidth(idealSize.Width());
355 }
356 }
357 childLayoutConstraint.parentIdealSize = OptionalSizeF(parentIdealSize);
358 swiperWrapper->Measure(childLayoutConstraint);
359
360 return swiperWrapper->GetGeometryNode()->GetMarginFrameSize();
361 }
362 } // namespace OHOS::Ace::NG
363