• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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