1 /*
2 * Copyright (c) 2024 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/navigation/navigation_layout_util.h"
17
18 #include "core/components/common/layout/grid_system_manager.h"
19 #include "core/components_ng/pattern/navigation/navdestination_node_base.h"
20 #include "core/components_ng/pattern/navigation/navdestination_pattern_base.h"
21 #include "core/components_ng/pattern/navigation/nav_bar_pattern.h"
22 #include "core/components_ng/pattern/navigation/title_bar_pattern.h"
23 #include "core/components_ng/pattern/navigation/tool_bar_node.h"
24 #include "core/components_ng/pattern/navigation/navigation_title_util.h"
25
26 namespace OHOS::Ace::NG {
27 namespace {
GetRootWidth(const RefPtr<NavDestinationNodeBase> & nodeBase)28 double GetRootWidth(const RefPtr<NavDestinationNodeBase>& nodeBase)
29 {
30 double fallbackWidth = ScreenSystemManager::GetInstance().GetScreenWidth();
31 CHECK_NULL_RETURN(nodeBase, fallbackWidth);
32 auto context = nodeBase->GetContextRefPtr();
33 CHECK_NULL_RETURN(context, fallbackWidth);
34 auto rotateAngle = nodeBase->GetPageRotateAngle();
35 auto config = nodeBase->GetPageViewportConfig();
36 if (!config || !rotateAngle.has_value() ||
37 rotateAngle.value() == ROTATION_0 || rotateAngle.value() == ROTATION_180) {
38 return GridSystemManager::GetInstance().GetScreenWidth(context);
39 }
40 return config->GetWidth();
41 }
42 }
43
CheckWhetherNeedToHideToolbar(const RefPtr<NavDestinationNodeBase> & nodeBase,const SizeF & navigationSize)44 bool NavigationLayoutUtil::CheckWhetherNeedToHideToolbar(
45 const RefPtr<NavDestinationNodeBase>& nodeBase, const SizeF& navigationSize)
46 {
47 // if current menu or toolBar is custom, no need to hide.
48 if (nodeBase->GetPrevMenuIsCustomValue(false) || nodeBase->GetPrevToolBarIsCustom().value_or(false)) {
49 return false;
50 }
51
52 auto toolbarNode = AceType::DynamicCast<NavToolbarNode>(nodeBase->GetToolBarNode());
53 CHECK_NULL_RETURN(toolbarNode, false);
54
55 if (!EnableToolBarAdaptation(nodeBase)) {
56 TAG_LOGI(AceLogTag::ACE_NAVIGATION, "The toolbar adaptation has been closed");
57 return false;
58 }
59
60 if (!toolbarNode->HasValidContent()) {
61 return true;
62 }
63
64 auto theme = NavigationGetTheme();
65 CHECK_NULL_RETURN(theme, false);
66 auto rotationLimitCount = theme->GetToolbarRotationLimitGridCount();
67
68 RefPtr<GridColumnInfo> columnInfo;
69 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::NAVIGATION_TOOLBAR);
70 CHECK_NULL_RETURN(columnInfo, false);
71 auto columnInfoParent = columnInfo->GetParent();
72 CHECK_NULL_RETURN(columnInfoParent, false);
73 columnInfoParent->BuildColumnWidth(GetRootWidth(nodeBase));
74
75 auto currentColumns = columnInfoParent->GetColumns();
76 float gridWidth = static_cast<float>(columnInfo->GetWidth(rotationLimitCount));
77 float gutterWidth = columnInfoParent->GetGutterWidth().ConvertToPx();
78 float hideLimitWidth = gridWidth + gutterWidth * 2;
79 if (SystemProperties::GetDeviceType() == DeviceType::PHONE) {
80 if (currentColumns >= static_cast<int32_t>(rotationLimitCount) &&
81 GreatOrEqual(navigationSize.Width(), gridWidth)) {
82 return true;
83 }
84 } else if (SystemProperties::GetDeviceType() == DeviceType::TABLET) {
85 if (currentColumns > static_cast<int32_t>(rotationLimitCount) &&
86 GreatNotEqual(navigationSize.Width(), hideLimitWidth)) {
87 return true;
88 }
89 }
90 return false;
91 }
92
EnableToolBarAdaptation(const RefPtr<NavDestinationNodeBase> & nodeBase)93 bool NavigationLayoutUtil::EnableToolBarAdaptation(const RefPtr<NavDestinationNodeBase>& nodeBase)
94 {
95 auto navigationNode = AceType::DynamicCast<NavigationGroupNode>(nodeBase->GetNavigationNode());
96 if (navigationNode) {
97 auto navigatonLayoutProperty = navigationNode->GetLayoutProperty<NavigationLayoutProperty>();
98 if (navigatonLayoutProperty) {
99 return navigatonLayoutProperty->GetEnableToolBarAdaptationValue(true);
100 }
101 }
102 return true;
103 }
104
UpdateTitleBarMenuNode(const RefPtr<NavDestinationNodeBase> & nodeBase,const SizeF & navigationSize)105 void NavigationLayoutUtil::UpdateTitleBarMenuNode(
106 const RefPtr<NavDestinationNodeBase>& nodeBase, const SizeF& navigationSize)
107 {
108 auto pattern = nodeBase->GetPattern<NavDestinationPatternBase>();
109 CHECK_NULL_VOID(pattern);
110 if (nodeBase->GetPrevMenuIsCustomValue(false)) {
111 return;
112 }
113
114 auto titleBarNode = AceType::DynamicCast<TitleBarNode>(nodeBase->GetTitleBarNode());
115 CHECK_NULL_VOID(titleBarNode);
116 auto toolBarNode = AceType::DynamicCast<FrameNode>(nodeBase->GetToolBarNode());
117 CHECK_NULL_VOID(toolBarNode);
118 auto toolBarLayoutProperty = toolBarNode->GetLayoutProperty<LayoutProperty>();
119 CHECK_NULL_VOID(toolBarLayoutProperty);
120 auto toolBarDivider = AceType::DynamicCast<FrameNode>(nodeBase->GetToolBarDividerNode());
121 RefPtr<LayoutProperty> toolBarDividerProperty = nullptr;
122 if (toolBarDivider) {
123 toolBarDividerProperty = toolBarDivider->GetLayoutProperty();
124 }
125 auto isHideToolbar = pattern->GetToolbarHideStatus();
126 auto preMenuNode = titleBarNode->GetMenu();
127 bool needHide = CheckWhetherNeedToHideToolbar(nodeBase, navigationSize);
128 bool isNeedLandscapeMenu = needHide && !isHideToolbar;
129 pattern->SetIsNeedHideToolBarForNavWidth(needHide);
130 if (isNeedLandscapeMenu) {
131 toolBarLayoutProperty->UpdateVisibility(VisibleType::GONE);
132 if (toolBarDividerProperty) {
133 toolBarDividerProperty->UpdateVisibility(VisibleType::GONE);
134 }
135 } else if (!isHideToolbar) {
136 toolBarLayoutProperty->UpdateVisibility(VisibleType::VISIBLE);
137 if (toolBarDividerProperty) {
138 toolBarDividerProperty->UpdateVisibility(VisibleType::VISIBLE);
139 }
140 }
141 RefPtr<UINode> newMenuNode = isNeedLandscapeMenu ? nodeBase->GetLandscapeMenu() : nodeBase->GetMenu();
142 if (preMenuNode == newMenuNode) {
143 return;
144 }
145 auto nodeBasePattern = nodeBase->GetPattern<NavDestinationPatternBase>();
146 CHECK_NULL_VOID(nodeBasePattern);
147 // Mark need update safeAreaPadding when need hide safe-area-padding-mode-toolBar by landscape menu
148 if (nodeBasePattern->GetToolBarStyle().value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING) {
149 nodeBasePattern->UpdateSafeAreaPaddingChanged(true);
150 }
151 titleBarNode->RemoveChild(preMenuNode);
152 titleBarNode->SetMenu(newMenuNode);
153 titleBarNode->AddChild(newMenuNode);
154 }
155
MeasureToolBar(LayoutWrapper * layoutWrapper,const RefPtr<NavDestinationNodeBase> & nodeBase,const RefPtr<NavDestinationLayoutPropertyBase> & layoutPropertyBase,const SizeF & navigationSize)156 float NavigationLayoutUtil::MeasureToolBar(LayoutWrapper* layoutWrapper, const RefPtr<NavDestinationNodeBase>& nodeBase,
157 const RefPtr<NavDestinationLayoutPropertyBase>& layoutPropertyBase, const SizeF& navigationSize)
158 {
159 auto navDestinationPatternBase = nodeBase->GetPattern<NavDestinationPatternBase>();
160 CHECK_NULL_RETURN(navDestinationPatternBase, 0.0f);
161 auto toolBarNode = nodeBase->GetToolBarNode();
162 CHECK_NULL_RETURN(toolBarNode, 0.0f);
163 auto index = nodeBase->GetChildIndexById(toolBarNode->GetId());
164 auto toolBarWrapper = layoutWrapper->GetOrCreateChildByIndex(index);
165 CHECK_NULL_RETURN(toolBarWrapper, 0.0f);
166 auto constraint = layoutPropertyBase->CreateChildConstraint();
167
168 /**
169 * In the follow scenarios, we need to set the toolBar size to zero.
170 * 1. ToolBar has no child.
171 * 2. ToolBar is hidden and no toolBar animation is running.
172 * 3. ToolBar should be moved to the menu position in the Title.
173 */
174 auto translateState = layoutPropertyBase->GetToolBarTranslateStateValue(BarTranslateState::NONE);
175 if ((translateState == BarTranslateState::NONE &&
176 layoutPropertyBase->GetHideToolBar().value_or(false)) || toolBarNode->GetChildren().empty() ||
177 CheckWhetherNeedToHideToolbar(nodeBase, navigationSize)) {
178 constraint.selfIdealSize = OptionalSizeF(0.0f, 0.0f);
179 toolBarWrapper->Measure(constraint);
180 return 0.0f;
181 }
182
183 auto theme = NavigationGetTheme();
184 CHECK_NULL_RETURN(theme, 0.0f);
185 auto toolbarHeight = theme->GetToolbarHeigth();
186 constraint.selfIdealSize = OptionalSizeF(navigationSize.Width(), static_cast<float>(toolbarHeight.ConvertToPx()));
187 toolBarWrapper->Measure(constraint);
188 auto toolbarHeightAfterMeasure = toolBarWrapper->GetGeometryNode()->GetFrameSize().Height();
189 return static_cast<float>(toolbarHeightAfterMeasure);
190 }
191
MeasureToolBarDivider(LayoutWrapper * layoutWrapper,const RefPtr<NavDestinationNodeBase> & nodeBase,const RefPtr<NavDestinationLayoutPropertyBase> & layoutPropertyBase,const SizeF & navigationSize,float toolBarHeight)192 float NavigationLayoutUtil::MeasureToolBarDivider(
193 LayoutWrapper* layoutWrapper, const RefPtr<NavDestinationNodeBase>& nodeBase,
194 const RefPtr<NavDestinationLayoutPropertyBase>& layoutPropertyBase,
195 const SizeF& navigationSize, float toolBarHeight)
196 {
197 if (nodeBase->GetPrevToolBarIsCustom().value_or(false) || !nodeBase->IsUseToolbarConfiguration()) {
198 return 0.0f;
199 }
200
201 auto navDestinationPatternBase = nodeBase->GetPattern<NavDestinationPatternBase>();
202 CHECK_NULL_RETURN(navDestinationPatternBase, 0.0f);
203 auto toolBarDividerNode = AceType::DynamicCast<FrameNode>(nodeBase->GetToolBarDividerNode());
204 CHECK_NULL_RETURN(toolBarDividerNode, 0.0f);
205 auto dividerIndex = nodeBase->GetChildIndexById(toolBarDividerNode->GetId());
206 auto dividerWrapper = layoutWrapper->GetOrCreateChildByIndex(dividerIndex);
207 CHECK_NULL_RETURN(dividerWrapper, 0.0f);
208 auto constraint = layoutPropertyBase->CreateChildConstraint();
209
210 /**
211 * In the follow scenarios, we need to set the toolBarDivider size to zero.
212 * 1. ToolBar has zero size.
213 * 2. ToolBar is hidden and no toolBar animation is running.
214 */
215 auto translateState = layoutPropertyBase->GetToolBarTranslateStateValue(BarTranslateState::NONE);
216 if ((translateState == BarTranslateState::NONE &&
217 layoutPropertyBase->GetHideToolBar().value_or(false)) || NearEqual(toolBarHeight, 0.0f)) {
218 constraint.selfIdealSize = OptionalSizeF(0.0f, 0.0f);
219 dividerWrapper->Measure(constraint);
220 return 0.0f;
221 }
222 auto theme = NavigationGetTheme();
223 CHECK_NULL_RETURN(theme, 0.0f);
224 constraint.selfIdealSize =
225 OptionalSizeF(navigationSize.Width(), static_cast<float>(theme->GetToolBarDividerWidth().ConvertToPx()));
226 dividerWrapper->Measure(constraint);
227 return static_cast<float>(theme->GetToolBarDividerWidth().ConvertToPx());
228 }
229
LayoutToolBar(LayoutWrapper * layoutWrapper,const RefPtr<NavDestinationNodeBase> & nodeBase,const RefPtr<NavDestinationLayoutPropertyBase> & layoutPropertyBase,bool isNeedToCreatePaddingAndBorder)230 float NavigationLayoutUtil::LayoutToolBar(LayoutWrapper* layoutWrapper, const RefPtr<NavDestinationNodeBase>& nodeBase,
231 const RefPtr<NavDestinationLayoutPropertyBase>& layoutPropertyBase, bool isNeedToCreatePaddingAndBorder)
232 {
233 /**
234 * When all the following conditions are met, we consider the boolBar height to be 0:
235 * 1. ToolBar should hide.
236 * 2. No toolBar animation is running or toolBar was translate out of navigation area.
237 */
238 auto translateState = layoutPropertyBase->GetToolBarTranslateStateValue(BarTranslateState::NONE);
239 if (translateState != BarTranslateState::TRANSLATE_ZERO && layoutPropertyBase->GetHideToolBar().value_or(false)) {
240 return 0.0f;
241 }
242 auto toolBarNode = nodeBase->GetToolBarNode();
243 CHECK_NULL_RETURN(toolBarNode, 0.0f);
244 auto index = nodeBase->GetChildIndexById(toolBarNode->GetId());
245 auto toolBarWrapper = layoutWrapper->GetOrCreateChildByIndex(index);
246 CHECK_NULL_RETURN(toolBarWrapper, 0.0f);
247 auto geometryNode = toolBarWrapper->GetGeometryNode();
248 float toolbarHeight = geometryNode->GetFrameSize().Height();
249 if (NearZero(toolbarHeight)) {
250 return 0.0f;
251 }
252 auto toolBarOffsetX = geometryNode->GetFrameOffset().GetX();
253 auto toolBarOffsetY = layoutWrapper->GetGeometryNode()->GetFrameSize().Height() - toolbarHeight;
254 if (isNeedToCreatePaddingAndBorder) {
255 const auto& padding = layoutPropertyBase->CreatePaddingAndBorder();
256 toolBarOffsetX = padding.left.value_or(0.0f);
257 toolBarOffsetY -= padding.bottom.value_or(0.0f);
258 }
259 auto toolBarOffset = OffsetF(static_cast<float>(toolBarOffsetX), static_cast<float>(toolBarOffsetY));
260 geometryNode->SetMarginFrameOffset(toolBarOffset);
261 toolBarWrapper->Layout();
262 return toolbarHeight;
263 }
264
LayoutToolBarDivider(LayoutWrapper * layoutWrapper,const RefPtr<NavDestinationNodeBase> & nodeBase,const RefPtr<NavDestinationLayoutPropertyBase> & layoutPropertyBase,float toolbarHeight,bool isNeedToCreatePaddingAndBorder)265 void NavigationLayoutUtil::LayoutToolBarDivider(
266 LayoutWrapper* layoutWrapper, const RefPtr<NavDestinationNodeBase>& nodeBase,
267 const RefPtr<NavDestinationLayoutPropertyBase>& layoutPropertyBase, float toolbarHeight,
268 bool isNeedToCreatePaddingAndBorder)
269 {
270 /**
271 * In the follow scenarios, we should not layout the toolBarDivider:
272 * 1. ToolBar has zero size.
273 * 2. Developer use the deprecated `toolBar` attr.
274 * 3. Custom toolbar was used.
275 * 4. Hide toolbar, no toolBar animation is running or toolBar was translate out of navigation area.
276 */
277 auto translateState = layoutPropertyBase->GetToolBarTranslateStateValue(BarTranslateState::NONE);
278 if ((translateState != BarTranslateState::TRANSLATE_ZERO && layoutPropertyBase->GetHideToolBar().value_or(false)) ||
279 nodeBase->GetPrevToolBarIsCustom().value_or(false) ||
280 !nodeBase->IsUseToolbarConfiguration() || NearZero(toolbarHeight)) {
281 return;
282 }
283 auto dividerNode = nodeBase->GetToolBarDividerNode();
284 CHECK_NULL_VOID(dividerNode);
285 auto dividerIndex = nodeBase->GetChildIndexById(dividerNode->GetId());
286 auto dividerWrapper = layoutWrapper->GetOrCreateChildByIndex(dividerIndex);
287 CHECK_NULL_VOID(dividerWrapper);
288 auto dividerGeometryNode = dividerWrapper->GetGeometryNode();
289
290 auto theme = NavigationGetTheme();
291 CHECK_NULL_VOID(theme);
292 auto dividerOffsetX = dividerGeometryNode->GetFrameOffset().GetX();
293 auto dividerOffsetY = layoutWrapper->GetGeometryNode()->GetFrameSize().Height() - toolbarHeight -
294 theme->GetToolBarDividerWidth().ConvertToPx();
295 if (isNeedToCreatePaddingAndBorder) {
296 const auto& padding = layoutPropertyBase->CreatePaddingAndBorder();
297 dividerOffsetX = padding.left.value_or(0.0f);
298 dividerOffsetY -= padding.bottom.value_or(0.0f);
299 }
300 auto toolBarDividerOffset = OffsetF(static_cast<float>(dividerOffsetX), static_cast<float>(dividerOffsetY));
301 dividerGeometryNode->SetFrameOffset(toolBarDividerOffset);
302 dividerWrapper->Layout();
303 }
304
UpdateContentSafeAreaPadding(const RefPtr<NavDestinationNodeBase> & nodeBase,float titleBarHeight)305 void NavigationLayoutUtil::UpdateContentSafeAreaPadding(
306 const RefPtr<NavDestinationNodeBase>& nodeBase, float titleBarHeight)
307 {
308 CHECK_NULL_VOID(nodeBase);
309 auto patternBase = nodeBase->GetPattern<NavDestinationPatternBase>();
310 CHECK_NULL_VOID(patternBase);
311 if (!patternBase->IsSafeAreaPaddingChanged()) {
312 return;
313 }
314 patternBase->UpdateSafeAreaPaddingChanged(false);
315 auto contentNode = AceType::DynamicCast<FrameNode>(nodeBase->GetContentNode());
316 CHECK_NULL_VOID(contentNode);
317 auto contentLayoutProperty = contentNode->GetLayoutProperty();
318 CHECK_NULL_VOID(contentLayoutProperty);
319
320 Dimension paddingTop = 0.0_vp;
321 if (patternBase->GetTitleBarStyle().value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING &&
322 !NavigationTitleUtil::IsTitleBarHasOffsetY(AceType::DynamicCast<FrameNode>(nodeBase->GetTitleBarNode()))) {
323 paddingTop = Dimension(titleBarHeight);
324 }
325 Dimension paddingBottom = 0.0_vp;
326 auto toolBarNode = AceType::DynamicCast<FrameNode>(nodeBase->GetToolBarNode());
327 if (patternBase->GetToolBarStyle().value_or(BarStyle::STANDARD) == BarStyle::SAFE_AREA_PADDING &&
328 toolBarNode && toolBarNode->IsVisible()) {
329 auto theme = NavigationGetTheme();
330 if (theme) {
331 paddingBottom = theme->GetToolbarHeigth();
332 }
333 }
334 PaddingProperty paddingProperty;
335 paddingProperty.left = CalcLength(0.0_vp);
336 paddingProperty.right = CalcLength(0.0_vp);
337 paddingProperty.top = CalcLength(paddingTop);
338 paddingProperty.bottom = CalcLength(paddingBottom);
339
340 contentLayoutProperty->UpdateSafeAreaPadding(paddingProperty);
341 }
342
UpdateConstraintWhenFixOrWrap(const RefPtr<LayoutProperty> & layoutProperty,LayoutConstraintF & constraint,SizeF size)343 void NavigationLayoutUtil::UpdateConstraintWhenFixOrWrap(
344 const RefPtr<LayoutProperty>& layoutProperty, LayoutConstraintF& constraint, SizeF size)
345 {
346 auto layoutPolicy = layoutProperty->GetLayoutPolicyProperty();
347 bool isWidthWrapOrFix =
348 layoutPolicy.has_value() ? layoutPolicy->IsWidthWrap() || layoutPolicy->IsWidthFix() : false;
349 bool isHeightWrapOrFix =
350 layoutPolicy.has_value() ? layoutPolicy->IsHeightWrap() || layoutPolicy->IsHeightFix() : false;
351 constraint.selfIdealSize = OptionalSizeF();
352 bool isAutoHeight = NavigationLayoutAlgorithm::IsAutoHeight(layoutProperty);
353 if (!isWidthWrapOrFix) {
354 constraint.selfIdealSize.SetWidth(size.Width());
355 }
356
357 if (!isHeightWrapOrFix && !isAutoHeight) {
358 constraint.selfIdealSize.SetHeight(size.Height());
359 }
360 }
CheckVerticalExtend(const RefPtr<NavDestinationLayoutPropertyBase> & layoutProperty,const RefPtr<NavDestinationNodeBase> & hostNode,const NG::IgnoreLayoutSafeAreaOpts & opts)361 std::pair<bool, bool> NavigationLayoutUtil::CheckVerticalExtend(
362 const RefPtr<NavDestinationLayoutPropertyBase>& layoutProperty, const RefPtr<NavDestinationNodeBase>& hostNode,
363 const NG::IgnoreLayoutSafeAreaOpts& opts)
364 {
365 auto defaultValue = std::make_pair(false, false);
366 if (!layoutProperty || !hostNode) {
367 return defaultValue;
368 }
369 const auto& padding = layoutProperty->CreatePaddingAndBorder();
370 float topPadding = padding.top.value_or(0.0f);
371 if (!NearEqual(topPadding, 0.0f)) {
372 return defaultValue;
373 }
374
375 bool isCanTopExtend = true;
376 isCanTopExtend &= ((opts.edges & LAYOUT_SAFE_AREA_EDGE_TOP) && (opts.type & LAYOUT_SAFE_AREA_TYPE_SYSTEM));
377 auto navBasePattern = hostNode->GetPattern<NavDestinationPatternBase>();
378 CHECK_NULL_RETURN(navBasePattern, defaultValue);
379 auto barStyle = navBasePattern->GetTitleBarStyle().value_or(BarStyle::STANDARD);
380 if (!(layoutProperty->GetHideTitleBar().value_or(false) || barStyle == BarStyle::STACK ||
381 (barStyle == BarStyle::SAFE_AREA_PADDING && !NearZero(navBasePattern->GetTitleBarOffsetY())))) {
382 isCanTopExtend = false;
383 }
384
385 bool isCanBottomExtend = true;
386 isCanBottomExtend &= ((opts.edges & LAYOUT_SAFE_AREA_EDGE_BOTTOM) && (opts.type & LAYOUT_SAFE_AREA_TYPE_SYSTEM));
387 auto toolBarStyle = navBasePattern->GetToolBarStyle().value_or(BarStyle::STANDARD);
388 if (hostNode->IsToolBarVisible() && toolBarStyle != BarStyle::STACK) {
389 isCanBottomExtend = false;
390 }
391 return std::make_pair(isCanTopExtend, isCanBottomExtend);
392 }
393 } // namespace OHOS::Ace::NG
394