1 /*
2 * Copyright (c) 2022 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/layout/layout_wrapper.h"
17
18 #include <algorithm>
19
20 #include "base/log/ace_checker.h"
21 #include "base/utils/utils.h"
22 #include "core/common/container.h"
23 #include "core/components/common/properties/alignment.h"
24 #include "core/components_ng/base/frame_node.h"
25 #include "core/components_ng/layout/layout_property.h"
26 #include "core/components_ng/layout/layout_wrapper_builder.h"
27 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
28 #include "core/components_ng/property/layout_constraint.h"
29 #include "core/components_ng/property/measure_property.h"
30 #include "core/components_ng/property/property.h"
31 #include "core/components_ng/property/safe_area_insets.h"
32 #include "core/components_v2/inspector/inspector_constants.h"
33 #include "core/pipeline_ng/pipeline_context.h"
34
35 namespace OHOS::Ace::NG {
36 namespace {
IsSyntaxNode(const std::string & tag)37 bool IsSyntaxNode(const std::string& tag)
38 {
39 return tag == V2::JS_VIEW_ETS_TAG || tag == V2::JS_IF_ELSE_ETS_TAG || tag == V2::JS_FOR_EACH_ETS_TAG ||
40 tag == V2::JS_LAZY_FOR_EACH_ETS_TAG || tag == V2::JS_SYNTAX_ITEM_ETS_TAG;
41 }
42 } // namespace
43
SkipMeasureContent() const44 bool LayoutWrapper::SkipMeasureContent() const
45 {
46 return skipMeasureContent_ == true;
47 }
48
ApplySafeArea(const SafeAreaInsets & insets,LayoutConstraintF & constraint)49 void LayoutWrapper::ApplySafeArea(const SafeAreaInsets& insets, LayoutConstraintF& constraint)
50 {
51 constraint.MinusPadding(
52 insets.left_.Length(), insets.right_.Length(), insets.top_.Length(), insets.bottom_.Length());
53 }
54
OffsetNodeToSafeArea()55 void LayoutWrapper::OffsetNodeToSafeArea()
56 {
57 auto&& insets = GetLayoutProperty()->GetSafeAreaInsets();
58 CHECK_NULL_VOID(insets);
59 auto geometryNode = GetGeometryNode();
60 auto offset = geometryNode->GetMarginFrameOffset();
61 if (offset.GetX() < insets->left_.end) {
62 offset.SetX(insets->left_.end);
63 }
64 if (offset.GetY() < insets->top_.end) {
65 offset.SetY(insets->top_.end);
66 }
67
68 auto right = offset.GetX() + geometryNode->GetMarginFrameSize().Width();
69 auto rightBound = insets->right_.IsValid() ? insets->right_.start : PipelineContext::GetCurrentRootWidth();
70 if (right > rightBound) {
71 offset.SetX(rightBound - geometryNode->GetMarginFrameSize().Width());
72 }
73 auto bottomBound = insets->bottom_.IsValid() ? insets->bottom_.start : PipelineContext::GetCurrentRootHeight();
74 auto bottom = offset.GetY() + geometryNode->GetMarginFrameSize().Height();
75 if (bottom > bottomBound) {
76 offset.SetY(bottomBound - geometryNode->GetMarginFrameSize().Height());
77 }
78 geometryNode->SetMarginFrameOffset(offset);
79 }
80
RestoreGeoState()81 void LayoutWrapper::RestoreGeoState()
82 {
83 if (GetGeometryNode()) {
84 GetGeometryNode()->Restore();
85 }
86 }
87
AvoidKeyboard(bool isFocusOnPage)88 void LayoutWrapper::AvoidKeyboard(bool isFocusOnPage)
89 {
90 // apply keyboard avoidance on Page
91 if (GetHostTag() == V2::PAGE_ETS_TAG) {
92 auto pipeline = PipelineContext::GetCurrentContext();
93 CHECK_NULL_VOID(pipeline);
94 auto manager = pipeline->GetSafeAreaManager();
95 if (!isFocusOnPage && LessNotEqual(manager->GetKeyboardOffset(), 0.0)) {
96 return;
97 }
98 auto safeArea = manager->GetSafeArea();
99 if (manager->IsAtomicService()) {
100 GetGeometryNode()->SetFrameOffset(OffsetF(0, manager->GetKeyboardOffset()));
101 return;
102 }
103 GetGeometryNode()->SetFrameOffset(OffsetF(0, safeArea.top_.Length() + manager->GetKeyboardOffset()));
104 }
105 }
106
SaveGeoState()107 void LayoutWrapper::SaveGeoState()
108 {
109 // save geometry state before SafeArea expansion / keyboard avoidance.
110 GetGeometryNode()->Save();
111 // record nodes whose geometry states need to be restored, to speed up RestoreGeoState
112 auto pipeline = PipelineContext::GetCurrentContext();
113 CHECK_NULL_VOID(pipeline);
114 auto manager = pipeline->GetSafeAreaManager();
115 manager->AddGeoRestoreNode(GetHostNode());
116 }
117
CheckValidSafeArea()118 bool LayoutWrapper::CheckValidSafeArea()
119 {
120 auto host = GetHostNode();
121 CHECK_NULL_RETURN(host, false);
122 auto pipeline = PipelineContext::GetCurrentContext();
123 CHECK_NULL_RETURN(pipeline, false);
124 auto safeAreaManager = pipeline->GetSafeAreaManager();
125 CHECK_NULL_RETURN(safeAreaManager, false);
126 SafeAreaInsets safeArea;
127 auto&& opts = GetLayoutProperty()->GetSafeAreaExpandOpts();
128 // if self does not have opts, check parent's
129 if (!opts) {
130 auto parent = host->GetAncestorNodeOfFrame();
131 CHECK_NULL_RETURN(parent, false);
132 CHECK_NULL_RETURN(parent->GetLayoutProperty(), false);
133 auto&& parentOpts = parent->GetLayoutProperty()->GetSafeAreaExpandOpts();
134 CHECK_NULL_RETURN(parentOpts, false);
135 safeArea = safeAreaManager->GetCombinedSafeArea(*parentOpts);
136 } else {
137 safeArea = safeAreaManager->GetCombinedSafeArea(*opts);
138 }
139 return safeArea.IsValid();
140 }
141
ExpandSafeArea(bool isFocusOnPage)142 void LayoutWrapper::ExpandSafeArea(bool isFocusOnPage)
143 {
144 auto host = GetHostNode();
145 CHECK_NULL_VOID(host);
146 auto&& opts = GetLayoutProperty()->GetSafeAreaExpandOpts();
147 auto selfExpansive = host->SelfExpansive();
148 if (!selfExpansive && !host->NeedRestoreSafeArea()) {
149 // if safeArea switch from valid to not valid, keep node restored and return
150 // otherwise if node restored, meaning expansive parent did not adjust child or so on, restore cache
151 if (CheckValidSafeArea()) {
152 auto syncCasheSuccess = GetGeometryNode()->RestoreCache();
153 auto renderContext = host->GetRenderContext();
154 CHECK_NULL_VOID(renderContext);
155 if (syncCasheSuccess) {
156 renderContext->SavePaintRect();
157 }
158 }
159 return;
160 }
161 CHECK_NULL_VOID(selfExpansive);
162 auto parent = host->GetAncestorNodeOfFrame();
163 if (parent && parent->GetPattern<ScrollablePattern>()) {
164 return;
165 }
166
167 if ((opts->edges & SAFE_AREA_EDGE_BOTTOM) && (opts->type & SAFE_AREA_TYPE_KEYBOARD) && isFocusOnPage) {
168 ExpandIntoKeyboard();
169 }
170
171 // get frame in global offset
172 auto parentGlobalOffset = host->GetParentGlobalOffsetDuringLayout();
173 auto geometryNode = GetGeometryNode();
174 auto frame = geometryNode->GetFrameRect() + parentGlobalOffset;
175 auto pipeline = PipelineContext::GetCurrentContext();
176 CHECK_NULL_VOID(pipeline);
177 auto safeArea = pipeline->GetSafeAreaManager()->GetCombinedSafeArea(*opts);
178 if ((opts->edges & SAFE_AREA_EDGE_START) && safeArea.left_.IsOverlapped(frame.Left())) {
179 frame.SetWidth(frame.Width() + frame.Left() - safeArea.left_.start);
180 frame.SetLeft(safeArea.left_.start);
181 }
182 if ((opts->edges & SAFE_AREA_EDGE_TOP) && safeArea.top_.IsOverlapped(frame.Top())) {
183 frame.SetHeight(frame.Height() + frame.Top() - safeArea.top_.start);
184 frame.SetTop(safeArea.top_.start);
185 }
186
187 if ((opts->edges & SAFE_AREA_EDGE_END) && safeArea.right_.IsOverlapped(frame.Right())) {
188 frame.SetWidth(frame.Width() + (safeArea.right_.end - frame.Right()));
189 }
190 if ((opts->edges & SAFE_AREA_EDGE_BOTTOM) && safeArea.bottom_.IsOverlapped(frame.Bottom())) {
191 frame.SetHeight(frame.Height() + (safeArea.bottom_.end - frame.Bottom()));
192 }
193
194 // reset if User has fixed size
195 auto layoutProperty = GetLayoutProperty();
196 if (layoutProperty->HasFixedWidth()) {
197 frame.SetWidth(geometryNode->GetFrameRect().Width());
198 }
199 if (layoutProperty->HasFixedHeight()) {
200 frame.SetHeight(geometryNode->GetFrameRect().Height());
201 }
202 if (layoutProperty->HasAspectRatio()) {
203 frame.SetHeight(frame.Width() / layoutProperty->GetAspectRatio());
204 }
205 // restore to local offset
206 frame -= parentGlobalOffset;
207 auto diff = geometryNode->GetFrameOffset() - frame.GetOffset();
208 if (!diff.NonOffset()) {
209 // children's position should remain the same.
210 AdjustChildren(diff);
211 } else {
212 RestoreExpansiveChildren();
213 }
214 geometryNode->SetFrameOffset(frame.GetOffset());
215 geometryNode->SetFrameSize(frame.GetSize());
216 host->SetNeedRestoreSafeArea(true);
217 auto renderContext = host->GetRenderContext();
218 CHECK_NULL_VOID(renderContext);
219 renderContext->SavePaintRect();
220 }
221
AdjustChildren(const OffsetF & offset)222 void LayoutWrapper::AdjustChildren(const OffsetF& offset)
223 {
224 for (const auto& childUI : GetHostNode()->GetChildren()) {
225 AdjustChild(childUI, offset);
226 }
227 }
228
AdjustChild(RefPtr<UINode> childUI,const OffsetF & offset)229 void LayoutWrapper::AdjustChild(RefPtr<UINode> childUI, const OffsetF& offset)
230 {
231 auto child = DynamicCast<FrameNode>(childUI);
232 if (!child) {
233 if (!IsSyntaxNode(childUI->GetTag())) {
234 return;
235 }
236 for (const auto& syntaxChild : childUI->GetChildren()) {
237 AdjustChild(syntaxChild, offset);
238 }
239 return;
240 }
241 if (child->NeedRestoreSafeArea()) {
242 return;
243 }
244 auto childGeo = child->GetGeometryNode();
245 child->SaveGeoState();
246 childGeo->SetFrameOffset(childGeo->GetFrameOffset() + offset);
247 child->SetNeedRestoreSafeArea(true);
248 auto renderContext = child->GetRenderContext();
249 CHECK_NULL_VOID(renderContext);
250 renderContext->SavePaintRect();
251 }
252
RestoreExpansiveChildren()253 void LayoutWrapper::RestoreExpansiveChildren()
254 {
255 for (const auto& childUI : GetHostNode()->GetChildren()) {
256 RestoreExpansiveChild(childUI);
257 }
258 }
259
RestoreExpansiveChild(const RefPtr<UINode> & childUI)260 void LayoutWrapper::RestoreExpansiveChild(const RefPtr<UINode>& childUI)
261 {
262 auto child = DynamicCast<FrameNode>(childUI);
263 if (!child) {
264 if (!IsSyntaxNode(childUI->GetTag())) {
265 return;
266 }
267 for (const auto& syntaxChild : childUI->GetChildren()) {
268 RestoreExpansiveChild(syntaxChild);
269 }
270 return;
271 }
272 if (!child->SelfExpansive() || !child->NeedRestoreSafeArea()) {
273 return;
274 }
275 auto childGeo = child->GetGeometryNode();
276 childGeo->Restore();
277 child->SetNeedRestoreSafeArea(false);
278 auto renderContext = child->GetRenderContext();
279 CHECK_NULL_VOID(renderContext);
280 renderContext->SavePaintRect();
281 }
282
ExpandIntoKeyboard()283 void LayoutWrapper::ExpandIntoKeyboard()
284 {
285 // if parent already expanded into keyboard, offset shouldn't be applied again
286 auto parent = GetHostNode()->GetAncestorNodeOfFrame();
287 while (parent) {
288 auto&& opts = parent->GetLayoutProperty()->GetSafeAreaExpandOpts();
289 if (opts && (opts->edges & SAFE_AREA_EDGE_BOTTOM) && opts->type & SAFE_AREA_TYPE_KEYBOARD) {
290 return;
291 }
292 parent = parent->GetAncestorNodeOfFrame();
293 }
294
295 auto pipeline = PipelineContext::GetCurrentContext();
296 CHECK_NULL_VOID(pipeline);
297 auto geometryNode = GetGeometryNode();
298 geometryNode->SetFrameOffset(
299 geometryNode->GetFrameOffset() - OffsetF(0, pipeline->GetSafeAreaManager()->GetKeyboardOffset()));
300 }
301
ApplyConstraint(LayoutConstraintF constraint)302 void LayoutWrapper::ApplyConstraint(LayoutConstraintF constraint)
303 {
304 GetGeometryNode()->SetParentLayoutConstraint(constraint);
305
306 auto layoutProperty = GetLayoutProperty();
307 auto& magicItemProperty = layoutProperty->GetMagicItemProperty();
308 if (magicItemProperty.HasAspectRatio()) {
309 std::optional<CalcSize> idealSize = std::nullopt;
310 if (layoutProperty->GetCalcLayoutConstraint()) {
311 idealSize = layoutProperty->GetCalcLayoutConstraint()->selfIdealSize;
312 }
313 auto greaterThanApiTen = Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TEN);
314 constraint.ApplyAspectRatio(magicItemProperty.GetAspectRatioValue(), idealSize, greaterThanApiTen);
315 }
316
317 auto&& insets = layoutProperty->GetSafeAreaInsets();
318 if (insets) {
319 ApplySafeArea(*insets, constraint);
320 }
321
322 layoutProperty->UpdateLayoutConstraint(constraint);
323 }
324
CreateRootConstraint()325 void LayoutWrapper::CreateRootConstraint()
326 {
327 LayoutConstraintF layoutConstraint;
328 layoutConstraint.percentReference.SetWidth(PipelineContext::GetCurrentRootWidth());
329 auto layoutProperty = GetLayoutProperty();
330 auto& magicItemProperty = layoutProperty->GetMagicItemProperty();
331 if (magicItemProperty.HasAspectRatio()) {
332 auto aspectRatio = magicItemProperty.GetAspectRatioValue();
333 if (Positive(aspectRatio)) {
334 auto height = PipelineContext::GetCurrentRootHeight() / aspectRatio;
335 layoutConstraint.percentReference.SetHeight(height);
336 }
337 } else {
338 layoutConstraint.percentReference.SetHeight(PipelineContext::GetCurrentRootHeight());
339 }
340 layoutProperty->UpdateLayoutConstraint(layoutConstraint);
341 }
342
GetHostNode() const343 RefPtr<FrameNode> LayoutWrapper::GetHostNode() const
344 {
345 return hostNode_.Upgrade();
346 }
347
AddNodeFlexLayouts()348 void LayoutWrapper::AddNodeFlexLayouts()
349 {
350 if (!AceChecker::IsPerformanceCheckEnabled()) {
351 return;
352 }
353 auto host = GetHostNode();
354 CHECK_NULL_VOID(host);
355 auto frameNodeParent = host->GetAncestorNodeOfFrame();
356 CHECK_NULL_VOID(frameNodeParent);
357 if (frameNodeParent->GetTag() == V2::FLEX_ETS_TAG) {
358 auto parent = host->GetParent();
359 CHECK_NULL_VOID(parent);
360 if (parent->GetTag() == V2::JS_VIEW_ETS_TAG) {
361 parent->AddFlexLayouts();
362 } else if (host->GetTag() == V2::COMMON_VIEW_ETS_TAG) {
363 auto children = host->GetChildren();
364 if (!children.empty()) {
365 auto begin = children.begin();
366 (*begin)->AddFlexLayouts();
367 }
368 } else {
369 host->AddFlexLayouts();
370 }
371 }
372 }
373
AddNodeLayoutTime(int64_t time)374 void LayoutWrapper::AddNodeLayoutTime(int64_t time)
375 {
376 if (!AceChecker::IsPerformanceCheckEnabled()) {
377 return;
378 }
379 auto host = GetHostNode();
380 CHECK_NULL_VOID(host);
381 host->SetLayoutTime(time);
382 }
383
384 } // namespace OHOS::Ace::NG
385