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/pattern/dialog/dialog_layout_algorithm.h"
17
18 #include "base/geometry/dimension_offset.h"
19 #include "base/geometry/ng/point_t.h"
20 #include "base/geometry/ng/size_t.h"
21 #include "base/memory/ace_type.h"
22 #include "base/subwindow/subwindow_manager.h"
23 #include "base/utils/device_config.h"
24 #include "base/utils/system_properties.h"
25 #include "base/utils/utils.h"
26 #include "core/components/common/layout/grid_system_manager.h"
27 #include "core/components/common/properties/placement.h"
28 #include "core/components/dialog/dialog_theme.h"
29 #include "core/components_ng/base/frame_node.h"
30 #include "core/components_ng/layout/layout_algorithm.h"
31 #include "core/components_ng/pattern/dialog/dialog_layout_property.h"
32 #include "core/components_ng/pattern/dialog/dialog_pattern.h"
33 #include "core/components_ng/pattern/scroll/scroll_layout_property.h"
34 #include "core/components_ng/pattern/text/text_layout_algorithm.h"
35 #include "core/components_ng/property/measure_utils.h"
36 #include "core/components_ng/render/paragraph.h"
37 #include "core/components_v2/inspector/inspector_constants.h"
38 #include "core/pipeline/base/constants.h"
39 #include "core/pipeline_ng/pipeline_context.h"
40 #include "core/pipeline_ng/ui_task_scheduler.h"
41
42 namespace OHOS::Ace::NG {
43 namespace {
44
45 // Using UX spec: Constrain max height within 4/5 of screen height.
46 // TODO: move these values to theme.
47 constexpr double DIALOG_HEIGHT_RATIO = 0.8;
48 constexpr double DIALOG_HEIGHT_RATIO_FOR_LANDSCAPE = 0.9;
49 constexpr double DIALOG_HEIGHT_RATIO_FOR_CAR = 0.95;
50 constexpr Dimension listPaddingHeight = 48.0_vp;
51 constexpr int32_t PLATFORM_VERSION_TEN = 10;
52
53 } // namespace
54
Measure(LayoutWrapper * layoutWrapper)55 void DialogLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
56 {
57 CHECK_NULL_VOID(layoutWrapper);
58 auto pipeline = PipelineContext::GetCurrentContext();
59 CHECK_NULL_VOID(pipeline);
60 auto dialogProp = AceType::DynamicCast<DialogLayoutProperty>(layoutWrapper->GetLayoutProperty());
61 customSize_ = dialogProp->GetUseCustomStyle().value_or(false);
62 gridCount_ = dialogProp->GetGridCount().value_or(-1);
63 const auto& layoutConstraint = dialogProp->GetLayoutConstraint();
64 const auto& parentIdealSize = layoutConstraint->parentIdealSize;
65 OptionalSizeF realSize;
66 // dialog size fit screen.
67 realSize.UpdateIllegalSizeWithCheck(parentIdealSize);
68 layoutWrapper->GetGeometryNode()->SetFrameSize(realSize.ConvertToSizeT());
69 layoutWrapper->GetGeometryNode()->SetContentSize(realSize.ConvertToSizeT());
70 // update child layout constraint
71 auto childLayoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
72
73 // constraint child size unless developer is using customStyle
74 if (!customSize_) {
75 auto inset = pipeline->GetSafeArea();
76 auto maxSize = layoutConstraint->maxSize;
77 maxSize.MinusPadding(0, 0, inset.top_.Length(), 0);
78 childLayoutConstraint.UpdateMaxSizeWithCheck(maxSize);
79 ComputeInnerLayoutParam(childLayoutConstraint);
80 }
81 const auto& children = layoutWrapper->GetAllChildrenWithBuild();
82 if (children.empty()) {
83 return;
84 }
85 auto child = children.front();
86 // childSize_ and childOffset_ is used in Layout.
87 child->Measure(childLayoutConstraint);
88
89 if (!layoutWrapper->GetHostNode()->GetPattern<DialogPattern>()->GetCustomNode()) {
90 AnalysisHeightOfChild(layoutWrapper);
91 }
92 }
93
AnalysisHeightOfChild(LayoutWrapper * layoutWrapper)94 void DialogLayoutAlgorithm::AnalysisHeightOfChild(LayoutWrapper* layoutWrapper)
95 {
96 float scrollHeight = 0.0f;
97 float listHeight = 0.0f;
98 float restHeight = 0.0f;
99 float restWidth = 0.0f;
100 RefPtr<LayoutWrapper> scroll;
101 RefPtr<LayoutWrapper> list;
102 for (const auto& children : layoutWrapper->GetAllChildrenWithBuild()) {
103 restWidth = children->GetGeometryNode()->GetMarginFrameSize().Width();
104 restHeight = children->GetGeometryNode()->GetMarginFrameSize().Height();
105 for (const auto& grandson : children->GetAllChildrenWithBuild()) {
106 if (grandson->GetHostTag() == V2::SCROLL_ETS_TAG) {
107 scroll = grandson;
108 scrollHeight = grandson->GetGeometryNode()->GetMarginFrameSize().Height();
109 } else if (grandson->GetHostTag() == V2::LIST_ETS_TAG) {
110 list = grandson;
111 listHeight = grandson->GetGeometryNode()->GetMarginFrameSize().Height();
112 } else {
113 restHeight -= grandson->GetGeometryNode()->GetMarginFrameSize().Height();
114 }
115 }
116 }
117
118 if (scroll != nullptr) {
119 AnalysisLayoutOfContent(layoutWrapper, scroll);
120 }
121
122 if (scroll != nullptr && list != nullptr) {
123 Distribute(scrollHeight, listHeight, restHeight);
124 auto childConstraint = CreateDialogChildConstraint(layoutWrapper, scrollHeight, restWidth);
125 scroll->Measure(childConstraint);
126 childConstraint =
127 CreateDialogChildConstraint(layoutWrapper, listHeight + listPaddingHeight.ConvertToPx(), restWidth);
128 list->Measure(childConstraint);
129 } else {
130 if (scroll != nullptr) {
131 auto childConstraint =
132 CreateDialogChildConstraint(layoutWrapper, std::min(restHeight, scrollHeight), restWidth);
133 scroll->Measure(childConstraint);
134 }
135 if (list != nullptr) {
136 auto childConstraint = CreateDialogChildConstraint(
137 layoutWrapper, std::min(restHeight, listHeight + (float)listPaddingHeight.ConvertToPx()), restWidth);
138 list->Measure(childConstraint);
139 }
140 }
141 }
142
AnalysisLayoutOfContent(LayoutWrapper * layoutWrapper,const RefPtr<LayoutWrapper> & scroll)143 void DialogLayoutAlgorithm::AnalysisLayoutOfContent(LayoutWrapper* layoutWrapper, const RefPtr<LayoutWrapper>& scroll)
144 {
145 auto text = scroll->GetAllChildrenWithBuild().front();
146 CHECK_NULL_VOID(text);
147 auto layoutAlgorithmWrapper = DynamicCast<LayoutAlgorithmWrapper>(text->GetLayoutAlgorithm());
148 CHECK_NULL_VOID(layoutAlgorithmWrapper);
149 auto textLayoutAlgorithm = DynamicCast<TextLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
150 CHECK_NULL_VOID(textLayoutAlgorithm);
151 auto hostNode = layoutWrapper->GetHostNode();
152 CHECK_NULL_VOID(hostNode);
153 auto dialogPattern = hostNode->GetPattern<DialogPattern>();
154 CHECK_NULL_VOID(dialogPattern);
155 if (dialogPattern->GetTitle().empty() && dialogPattern->GetSubtitle().empty()) {
156 scroll->GetLayoutProperty()->UpdateAlignment(Alignment::CENTER);
157 } else {
158 scroll->GetLayoutProperty()->UpdateAlignment(Alignment::CENTER_LEFT);
159 }
160 }
161
Distribute(float & scrollHeight,float & listHeight,float restHeight)162 void DialogLayoutAlgorithm::Distribute(float& scrollHeight, float& listHeight, float restHeight)
163 {
164 if (scrollHeight + listHeight > restHeight) {
165 if (scrollHeight > restHeight / 2.0 && listHeight > restHeight / 2.0) {
166 scrollHeight = restHeight / 2.0;
167 listHeight = restHeight / 2.0;
168 } else if (scrollHeight > restHeight / 2.0) {
169 scrollHeight = restHeight - listHeight;
170 } else {
171 listHeight = restHeight - scrollHeight;
172 }
173 }
174 }
175
CreateDialogChildConstraint(LayoutWrapper * layoutWrapper,float height,float width)176 LayoutConstraintF DialogLayoutAlgorithm::CreateDialogChildConstraint(
177 LayoutWrapper* layoutWrapper, float height, float width)
178 {
179 auto childConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
180 childConstraint.minSize.SetHeight(height);
181 childConstraint.maxSize.SetHeight(height);
182 childConstraint.percentReference.SetHeight(height);
183 childConstraint.minSize.SetWidth(width);
184 childConstraint.maxSize.SetWidth(width);
185 childConstraint.percentReference.SetWidth(width);
186 return childConstraint;
187 }
188
ComputeInnerLayoutParam(LayoutConstraintF & innerLayout)189 void DialogLayoutAlgorithm::ComputeInnerLayoutParam(LayoutConstraintF& innerLayout)
190 {
191 auto maxSize = innerLayout.maxSize;
192 // Set different layout param for different devices
193 // TODO: need to use theme json to replace this function.
194 // get grid size type based on the screen where the dialog locate
195 auto gridSizeType = ScreenSystemManager::GetInstance().GetSize(maxSize.Width());
196 RefPtr<GridColumnInfo> columnInfo;
197 if (SystemProperties::GetDeviceType() == DeviceType::CAR) {
198 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::CAR_DIALOG);
199 } else {
200 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::DIALOG);
201 }
202 columnInfo->GetParent()->BuildColumnWidth(maxSize.Width());
203 auto width = GetMaxWidthBasedOnGridType(columnInfo, gridSizeType, SystemProperties::GetDeviceType());
204 if (SystemProperties::GetDeviceType() == DeviceType::WATCH) {
205 innerLayout.minSize = SizeF(width, 0.0);
206 innerLayout.maxSize = SizeF(width, maxSize.Height());
207 } else if (SystemProperties::GetDeviceType() == DeviceType::PHONE) {
208 if (SystemProperties::GetDeviceOrientation() == DeviceOrientation::LANDSCAPE) {
209 innerLayout.minSize = SizeF(width, 0.0);
210 innerLayout.maxSize = SizeF(width, maxSize.Height() * DIALOG_HEIGHT_RATIO_FOR_LANDSCAPE);
211 } else {
212 innerLayout.minSize = SizeF(width, 0.0);
213 innerLayout.maxSize = SizeF(width, maxSize.Height() * DIALOG_HEIGHT_RATIO);
214 }
215 } else if (SystemProperties::GetDeviceType() == DeviceType::CAR) {
216 innerLayout.minSize = SizeF(width, 0.0);
217 innerLayout.maxSize = SizeF(width, maxSize.Height() * DIALOG_HEIGHT_RATIO_FOR_CAR);
218 } else {
219 innerLayout.minSize = SizeF(width, 0.0);
220 innerLayout.maxSize = SizeF(width, maxSize.Height() * DIALOG_HEIGHT_RATIO);
221 }
222 // update percentRef
223 innerLayout.percentReference = innerLayout.maxSize;
224 }
225
GetMaxWidthBasedOnGridType(const RefPtr<GridColumnInfo> & info,GridSizeType type,DeviceType deviceType)226 double DialogLayoutAlgorithm::GetMaxWidthBasedOnGridType(
227 const RefPtr<GridColumnInfo>& info, GridSizeType type, DeviceType deviceType)
228 {
229 auto parentColumns = info->GetParent()->GetColumns();
230 if (gridCount_ >= 0) {
231 return info->GetWidth(std::min(gridCount_, parentColumns));
232 }
233
234 int32_t deviceColumns;
235 if (deviceType == DeviceType::WATCH) {
236 if (type == GridSizeType::SM) {
237 deviceColumns = 3;
238 } else if (type == GridSizeType::MD) {
239 deviceColumns = 4;
240 } else if (type == GridSizeType::LG) {
241 deviceColumns = 5;
242 } else {
243 LOGD("GetMaxWidthBasedOnGridType is undefined");
244 deviceColumns = 5;
245 }
246 } else if (deviceType == DeviceType::PHONE) {
247 if (type == GridSizeType::SM) {
248 deviceColumns = 4;
249 } else if (type == GridSizeType::MD) {
250 deviceColumns = 5;
251 } else if (type == GridSizeType::LG) {
252 deviceColumns = 6;
253 } else {
254 LOGD("GetMaxWidthBasedOnGridType is undefined");
255 deviceColumns = 6;
256 }
257 } else if (deviceType == DeviceType::CAR) {
258 if (type == GridSizeType::SM) {
259 deviceColumns = 4;
260 } else if (type == GridSizeType::MD) {
261 deviceColumns = 6;
262 } else if (type == GridSizeType::LG) {
263 deviceColumns = 8;
264 } else {
265 LOGD("GetMaxWidthBasedOnGridType is undefined");
266 deviceColumns = 8;
267 }
268 } else {
269 if (type == GridSizeType::SM) {
270 deviceColumns = 2;
271 } else if (type == GridSizeType::MD) {
272 deviceColumns = 3;
273 } else if (type == GridSizeType::LG) {
274 deviceColumns = 4;
275 } else {
276 LOGD("GetMaxWidthBasedOnGridType is undefined");
277 deviceColumns = 4;
278 }
279 }
280 return info->GetWidth(std::min(deviceColumns, parentColumns));
281 }
282
Layout(LayoutWrapper * layoutWrapper)283 void DialogLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
284 {
285 CHECK_NULL_VOID(layoutWrapper);
286 auto frameNode = layoutWrapper->GetHostNode();
287 CHECK_NULL_VOID(frameNode);
288 auto dialogProp = DynamicCast<DialogLayoutProperty>(layoutWrapper->GetLayoutProperty());
289 CHECK_NULL_VOID(dialogProp);
290 dialogOffset_ = dialogProp->GetDialogOffset().value_or(DimensionOffset());
291 alignment_ = dialogProp->GetDialogAlignment().value_or(DialogAlignment::DEFAULT);
292 auto selfSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
293 const auto& children = layoutWrapper->GetAllChildrenWithBuild();
294 if (children.empty()) {
295 return;
296 }
297 auto child = children.front();
298 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
299 topLeftPoint_ = ComputeChildPosition(childSize, dialogProp, selfSize);
300 UpdateTouchRegion();
301 child->GetGeometryNode()->SetMarginFrameOffset(topLeftPoint_);
302 child->Layout();
303 if (dialogProp->GetShowInSubWindowValue(false)) {
304 std::vector<Rect> rects;
305 auto rect = Rect(0.0f, 0.0f, selfSize.Width(), selfSize.Height());
306 rects.emplace_back(rect);
307 SubwindowManager::GetInstance()->SetHotAreas(rects, frameNode->GetId());
308 }
309 }
310
ComputeChildPosition(const SizeF & childSize,const RefPtr<DialogLayoutProperty> & prop,const SizeF & selfSize)311 OffsetF DialogLayoutAlgorithm::ComputeChildPosition(
312 const SizeF& childSize, const RefPtr<DialogLayoutProperty>& prop, const SizeF& selfSize)
313 {
314 OffsetF topLeftPoint;
315 auto pipelineContext = PipelineContext::GetCurrentContext();
316 CHECK_NULL_RETURN(pipelineContext, OffsetF());
317 auto dialogTheme = pipelineContext->GetTheme<DialogTheme>();
318 const auto& layoutConstraint = prop->GetLayoutConstraint();
319 CHECK_NULL_RETURN(dialogTheme, OffsetF());
320 auto dialogOffsetX =
321 ConvertToPx(CalcLength(dialogOffset_.GetX()), layoutConstraint->scaleProperty, selfSize.Width());
322 auto dialogOffsetY =
323 ConvertToPx(CalcLength(dialogOffset_.GetY()), layoutConstraint->scaleProperty, selfSize.Height());
324 OffsetF dialogOffset = OffsetF(dialogOffsetX.value_or(0.0), dialogOffsetY.value_or(0.0));
325 if (!SetAlignmentSwitch(layoutConstraint->maxSize, childSize, topLeftPoint)) {
326 topLeftPoint = OffsetF(layoutConstraint->maxSize.Width() - childSize.Width(),
327 layoutConstraint->maxSize.Height() - childSize.Height()) /
328 2.0;
329 }
330 const auto& expandSafeAreaOpts = prop->GetSafeAreaExpandOpts();
331 bool needAvoidKeyboard = true;
332 if (expandSafeAreaOpts && (expandSafeAreaOpts->type | SAFE_AREA_TYPE_KEYBOARD)) {
333 needAvoidKeyboard = false;
334 }
335 return AdjustChildPosition(topLeftPoint, dialogOffset, childSize, needAvoidKeyboard);
336 }
337
SetAlignmentSwitch(const SizeF & maxSize,const SizeF & childSize,OffsetF & topLeftPoint)338 bool DialogLayoutAlgorithm::SetAlignmentSwitch(const SizeF& maxSize, const SizeF& childSize, OffsetF& topLeftPoint)
339 {
340 if (alignment_ != DialogAlignment::DEFAULT) {
341 switch (alignment_) {
342 case DialogAlignment::TOP:
343 topLeftPoint = OffsetF((maxSize.Width() - childSize.Width()) / 2.0, 0.0);
344 break;
345 case DialogAlignment::CENTER:
346 topLeftPoint =
347 OffsetF(maxSize.Width() - childSize.Width(), maxSize.Height() - childSize.Height()) / 2.0;
348 break;
349 case DialogAlignment::BOTTOM:
350 topLeftPoint =
351 OffsetF((maxSize.Width() - childSize.Width()) / 2.0, maxSize.Height() - childSize.Height());
352 break;
353 case DialogAlignment::TOP_START:
354 topLeftPoint = OffsetF(0.0, 0.0);
355 break;
356 case DialogAlignment::TOP_END:
357 topLeftPoint = OffsetF(maxSize.Width() - childSize.Width(), 0.0);
358 break;
359 case DialogAlignment::CENTER_START:
360 topLeftPoint = OffsetF(0.0, maxSize.Height() - childSize.Height()) / 2.0;
361 break;
362 case DialogAlignment::CENTER_END:
363 topLeftPoint =
364 OffsetF(maxSize.Width() - childSize.Width(), (maxSize.Height() - childSize.Height()) / 2.0);
365 break;
366 case DialogAlignment::BOTTOM_START:
367 topLeftPoint = OffsetF(0.0, maxSize.Height() - childSize.Height());
368 break;
369 case DialogAlignment::BOTTOM_END:
370 topLeftPoint = OffsetF(maxSize.Width() - childSize.Width(), maxSize.Height() - childSize.Height());
371 break;
372 default:
373 topLeftPoint =
374 OffsetF(maxSize.Width() - childSize.Width(), maxSize.Height() - childSize.Height()) / 2.0;
375 break;
376 }
377 return true;
378 }
379 bool version10OrLarger = PipelineBase::GetCurrentContext()->GetMinPlatformVersion() >= PLATFORM_VERSION_TEN;
380 if (version10OrLarger && SystemProperties::GetDeviceType() == DeviceType::PHONE) {
381 if (SystemProperties::GetDeviceOrientation() == DeviceOrientation::LANDSCAPE) {
382 topLeftPoint = OffsetF(maxSize.Width() - childSize.Width(), maxSize.Height() - childSize.Height()) / 2.0;
383 return true;
384 }
385 if (SystemProperties::GetDeviceOrientation() == DeviceOrientation::PORTRAIT) {
386 topLeftPoint = OffsetF((maxSize.Width() - childSize.Width()) / 2.0,
387 maxSize.Height() - childSize.Height() - GetPaddingBottom());
388 return true;
389 }
390 }
391 return false;
392 }
393
UpdateTouchRegion()394 void DialogLayoutAlgorithm::UpdateTouchRegion()
395 {
396 // TODO: update touch region is not completed.
397 }
398
GetPaddingBottom() const399 double DialogLayoutAlgorithm::GetPaddingBottom() const
400 {
401 auto pipelineContext = PipelineContext::GetCurrentContext();
402 CHECK_NULL_RETURN(pipelineContext, 0);
403 auto dialogTheme = pipelineContext->GetTheme<DialogTheme>();
404 CHECK_NULL_RETURN(dialogTheme, 0);
405 auto bottom = dialogTheme->GetDefaultDialogMarginBottom();
406 return pipelineContext->NormalizeToPx(bottom);
407 }
408
AdjustChildPosition(OffsetF & topLeftPoint,const OffsetF & dialogOffset,const SizeF & childSize,bool needAvoidKeyboard) const409 OffsetF DialogLayoutAlgorithm::AdjustChildPosition(
410 OffsetF& topLeftPoint, const OffsetF& dialogOffset, const SizeF& childSize, bool needAvoidKeyboard) const
411 {
412 auto pipelineContext = PipelineContext::GetCurrentContext();
413 CHECK_NULL_RETURN(pipelineContext, topLeftPoint + dialogOffset);
414 auto systemInset = pipelineContext->GetSafeArea();
415 if (!customSize_ && topLeftPoint.GetY() < systemInset.top_.end) {
416 topLeftPoint.SetY(systemInset.top_.end);
417 }
418 auto childOffset = topLeftPoint + dialogOffset;
419
420 auto manager = pipelineContext->GetSafeAreaManager();
421 auto keyboardInsert = manager->GetKeyboardInset();
422 auto childBottom = childOffset.GetY() + childSize.Height();
423 auto paddingBottom = static_cast<float>(GetPaddingBottom());
424 if (needAvoidKeyboard && keyboardInsert.Length() > 0 && childBottom > (keyboardInsert.start - paddingBottom)) {
425 childOffset.SetY(childOffset.GetY() - (childBottom - (keyboardInsert.start - paddingBottom)));
426 }
427 return childOffset;
428 }
429 } // namespace OHOS::Ace::NG
430