1 /*
2  * Copyright (c) 2023 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/menu/menu_item/menu_item_layout_algorithm.h"
17 
18 #include "base/geometry/ng/size_t.h"
19 #include "base/utils/utils.h"
20 #include "core/components/select/select_theme.h"
21 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
22 #include "core/components_ng/property/measure_utils.h"
23 #include "core/pipeline/pipeline_base.h"
24 
25 namespace OHOS::Ace::NG {
Measure(LayoutWrapper * layoutWrapper)26 void MenuItemLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
27 {
28     CHECK_NULL_VOID(layoutWrapper);
29     auto pipeline = PipelineBase::GetCurrentContext();
30     CHECK_NULL_VOID(pipeline);
31     auto theme = pipeline->GetTheme<SelectTheme>();
32     CHECK_NULL_VOID(theme);
33     horInterval_ = static_cast<float>(theme->GetMenuIconPadding().ConvertToPx()) -
34                    static_cast<float>(theme->GetOutPadding().ConvertToPx());
35     middleSpace_ = static_cast<float>(theme->GetIconContentPadding().ConvertToPx());
36     auto props = layoutWrapper->GetLayoutProperty();
37     CHECK_NULL_VOID(props);
38 
39     auto layoutConstraint = props->GetLayoutConstraint();
40     CHECK_NULL_VOID(layoutConstraint);
41     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
42         verInterval_ = GetMenuItemVerticalPadding() - GetBordersHeight(layoutWrapper);
43     }
44     const auto& padding = props->CreatePaddingAndBorderWithDefault(horInterval_, verInterval_, 0.0f, 0.0f);
45     maxRowWidth_ = layoutConstraint->maxSize.Width() - padding.Width();
46     // update ideal width if user defined
47     const auto& calcConstraint = props->GetCalcLayoutConstraint();
48     if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
49         calcConstraint->selfIdealSize.value().Width().has_value()) {
50         ScaleProperty scaleProperty;
51         if (layoutWrapper->GetGeometryNode() && layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()) {
52             scaleProperty = layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()->scaleProperty;
53         } else {
54             scaleProperty = layoutConstraint->scaleProperty;
55         }
56         layoutConstraint->selfIdealSize.SetWidth(
57             ConvertToPx(calcConstraint->selfIdealSize.value().Width()->GetDimension(), scaleProperty,
58                 layoutConstraint->percentReference.Width()));
59     }
60     if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
61         calcConstraint->selfIdealSize.value().Height().has_value()) {
62         idealHeight_ = calcConstraint->selfIdealSize.value().Height()->GetDimension().ConvertToPx();
63     }
64     if (layoutConstraint->selfIdealSize.Width().has_value()) {
65         maxRowWidth_ =
66             std::max(layoutConstraint->minSize.Width(),
67                 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value())) -
68             padding.Width();
69     }
70     CheckNeedMatchParent(layoutWrapper, layoutConstraint);
71     minRowWidth_ = layoutConstraint->minSize.Width();
72 
73     auto childConstraint = props->CreateChildConstraint();
74     minItemHeight_ = static_cast<float>(theme->GetOptionMinHeight().ConvertToPx());
75     // set item min height
76     childConstraint.minSize.SetHeight(Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ?
77         theme->GetMenuChildMinHeight().ConvertToPx() : minItemHeight_);
78 
79     iconSize_ = theme->GetIconSideLength().ConvertToPx();
80     MeasureItemViews(childConstraint, layoutConstraint, padding, layoutWrapper);
81 
82     auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
83     if (GreatNotEqual(idealWidth_, 0.0f)) {
84         layoutWrapper->GetGeometryNode()->SetFrameWidth(idealWidth_);
85         if (clickableArea) {
86             clickableArea->GetGeometryNode()->SetFrameWidth(idealWidth_);
87         }
88     }
89 
90     CheckNeedExpandContent(layoutWrapper, childConstraint);
91 }
92 
Layout(LayoutWrapper * layoutWrapper)93 void MenuItemLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
94 {
95     CHECK_NULL_VOID(layoutWrapper);
96     const auto& padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorderWithDefault(horInterval_,
97         verInterval_, 0.0f, 0.0f);
98 
99     auto layoutDirection = layoutWrapper->GetLayoutProperty()->GetNonAutoLayoutDirection();
100     auto leftRow = layoutWrapper->GetOrCreateChildByIndex(0);
101     auto leftRowSize = leftRow ? leftRow->GetGeometryNode()->GetFrameSize() : SizeT(0.0f, 0.0f);
102     auto rightRow = layoutWrapper->GetOrCreateChildByIndex(1);
103     auto rightRowSize = rightRow ? rightRow->GetGeometryNode()->GetFrameSize() : SizeT(0.0f, 0.0f);
104 
105     auto itemHeight = idealHeight_ > 0.0f ? idealHeight_ :
106         std::max(leftRowSize.Height(), rightRowSize.Height()) + padding.Height();
107     CHECK_NULL_VOID(leftRow);
108     float topSpace = (itemHeight - leftRowSize.Height() + GetBordersHeight(layoutWrapper)) / 2.0f;
109     leftRow->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT);
110     leftRow->GetGeometryNode()->SetMarginFrameOffset(OffsetF(padding.left.value_or(horInterval_), topSpace));
111     if (layoutDirection == TextDirection::RTL) {
112         leftRow->GetGeometryNode()->SetMarginFrameOffset(
113             OffsetF(layoutWrapper->GetGeometryNode()->GetFrameSize().Width() - padding.right.value_or(horInterval_) -
114                     leftRow->GetGeometryNode()->GetFrameSize().Width(),
115             topSpace));
116     }
117     leftRow->Layout();
118 
119     CHECK_NULL_VOID(rightRow);
120     topSpace = (itemHeight - rightRowSize.Height() + GetBordersHeight(layoutWrapper)) / 2.0f;
121     rightRow->GetGeometryNode()->SetMarginFrameOffset(
122         OffsetF(layoutWrapper->GetGeometryNode()->GetFrameSize().Width() - padding.right.value_or(horInterval_) -
123             rightRow->GetGeometryNode()->GetFrameSize().Width(), topSpace));
124     if (layoutDirection == TextDirection::RTL) {
125         rightRow->GetGeometryNode()->SetMarginFrameOffset(OffsetF(padding.left.value_or(horInterval_), topSpace));
126     }
127     rightRow->Layout();
128 
129     auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
130     CHECK_NULL_VOID(clickableArea);
131     clickableArea->Layout();
132 
133     auto expandableArea = layoutWrapper->GetOrCreateChildByIndex(EXPANDABLE_AREA_VIEW_INDEX);
134     CHECK_NULL_VOID(expandableArea);
135     expandableArea->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT);
136     expandableArea->GetGeometryNode()->SetMarginFrameOffset(OffsetF(
137         padding.left.value_or(horInterval_), itemHeight));
138     expandableArea->Layout();
139 }
140 
CheckNeedMatchParent(LayoutWrapper * layoutWrapper,std::optional<LayoutConstraintF> & layoutConstraint)141 void MenuItemLayoutAlgorithm::CheckNeedMatchParent(LayoutWrapper* layoutWrapper,
142     std::optional<LayoutConstraintF>& layoutConstraint)
143 {
144     auto menuNode = layoutWrapper->GetHostNode();
145     auto menuItemPattern = menuNode ? menuNode->GetPattern<MenuItemPattern>() : nullptr;
146     auto expandingMode = menuItemPattern ? menuItemPattern->GetExpandingMode() : SubMenuExpandingMode::STACK;
147     auto isSubMenu = menuItemPattern ? menuItemPattern->IsSubMenu() : false;
148     auto isEmbedded = menuItemPattern ? menuItemPattern->IsEmbedded() : false;
149     bool matchParent = (expandingMode == SubMenuExpandingMode::STACK && isSubMenu) ||
150                        (expandingMode == SubMenuExpandingMode::EMBEDDED && isEmbedded);
151     if (matchParent) {
152         auto width = layoutConstraint->maxSize.Width();
153         layoutConstraint->minSize.SetWidth(width);
154     }
155 }
156 
MeasureItemViews(LayoutConstraintF & childConstraint,std::optional<LayoutConstraintF> & layoutConstraint,PaddingPropertyF padding,LayoutWrapper * layoutWrapper)157 void MenuItemLayoutAlgorithm::MeasureItemViews(LayoutConstraintF& childConstraint,
158     std::optional<LayoutConstraintF>& layoutConstraint, PaddingPropertyF padding, LayoutWrapper* layoutWrapper)
159 {
160     auto leftRow = layoutWrapper->GetOrCreateChildByIndex(0);
161     CHECK_NULL_VOID(leftRow);
162     childConstraint.maxSize.SetWidth(leftRow->GetGeometryNode()->GetFrameSize().Width()
163         // Cannot cover left icon
164         ? maxRowWidth_ - middleSpace_ - static_cast<float>(iconSize_)
165         : maxRowWidth_);
166     // measure right row
167     float rightRowWidth = 0.0f;
168     float rightRowHeight = 0.0f;
169     auto rightRow = layoutWrapper->GetOrCreateChildByIndex(1);
170     if (rightRow) {
171         rightRow->Measure(childConstraint);
172         rightRowWidth = rightRow->GetGeometryNode()->GetMarginFrameSize().Width();
173         rightRowHeight = rightRow->GetGeometryNode()->GetMarginFrameSize().Height();
174     }
175     // measure left row
176     auto maxWidth = maxRowWidth_ - rightRowWidth - middleSpace_;
177     childConstraint.maxSize.SetWidth(maxWidth);
178     MeasureRow(leftRow, childConstraint);
179     float leftRowWidth = leftRow->GetGeometryNode()->GetMarginFrameSize().Width();
180     float leftRowHeight = leftRow->GetGeometryNode()->GetMarginFrameSize().Height();
181     float contentWidth = leftRowWidth + rightRowWidth + padding.Width() + middleSpace_;
182 
183     auto itemHeight = std::max(leftRowHeight, rightRowHeight) + padding.Height();
184     auto width = std::max(minRowWidth_, contentWidth);
185 
186     needExpandContent_ = false;
187     emptyWidth_ = 0.0f;
188     if (contentWidth < minRowWidth_) {
189         emptyWidth_ = minRowWidth_ - contentWidth;
190         needExpandContent_ = true;
191     }
192 
193     idealWidth_ = 0.0f;
194     if (layoutConstraint->selfIdealSize.Width().has_value()) {
195         idealWidth_ = std::max(layoutConstraint->minSize.Width(),
196             std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value()));
197 
198         float newLeftRowWidth = idealWidth_ - rightRowWidth - padding.Width() - middleSpace_;
199         if (newLeftRowWidth > leftRowWidth) {
200             emptyWidth_ = newLeftRowWidth - leftRowWidth;
201             needExpandContent_ = true;
202         }
203     }
204 
205     auto actualWidth = GreatNotEqual(idealWidth_, 0.0f) ? idealWidth_ : width;
206     childConstraint.minSize.SetWidth(actualWidth - padding.Width());
207     childConstraint.maxSize.SetWidth(actualWidth - padding.Width());
208 
209     auto expandableHeight = 0.0f;
210     auto expandableArea = layoutWrapper->GetOrCreateChildByIndex(EXPANDABLE_AREA_VIEW_INDEX);
211     if (expandableArea) {
212         expandableArea->Measure(childConstraint);
213         expandableHeight = expandableArea->GetGeometryNode()->GetMarginFrameSize().Height();
214     }
215 
216     UpdateSelfSize(layoutWrapper, actualWidth, itemHeight, expandableHeight);
217 }
218 
MeasureRow(const RefPtr<LayoutWrapper> & row,const LayoutConstraintF & constraint)219 void MenuItemLayoutAlgorithm::MeasureRow(const RefPtr<LayoutWrapper>& row, const LayoutConstraintF& constraint)
220 {
221     auto children = row->GetAllChildrenWithBuild();
222     CHECK_NULL_VOID(!children.empty());
223     auto pipeline = PipelineBase::GetCurrentContext();
224     CHECK_NULL_VOID(pipeline);
225     auto theme = pipeline->GetTheme<SelectTheme>();
226     CHECK_NULL_VOID(theme);
227     auto iconContentPadding = static_cast<float>(theme->GetIconContentPadding().ConvertToPx());
228 
229     float spaceWidth = constraint.maxSize.Width();
230     float rowWidth = 0.0f;
231     float rowHeight = Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ?
232 	theme->GetMenuChildMinHeight().ConvertToPx() : minItemHeight_;
233     for (const auto& child : children) {
234         if (child != children.back()) {
235             // not content node
236             child->Measure(constraint);
237         } else {
238             // content node update constraint max width
239             auto contentConstraint = constraint;
240             contentConstraint.maxSize.SetWidth(spaceWidth);
241             child->Measure(contentConstraint);
242         }
243         auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
244         spaceWidth -= childSize.Width() + iconContentPadding;
245         rowWidth += childSize.Width() + iconContentPadding;
246         rowHeight = std::max(rowHeight, childSize.Height());
247     }
248     rowWidth -= iconContentPadding;
249     row->GetGeometryNode()->SetFrameSize(SizeF(rowWidth, rowHeight));
250 }
251 
CheckNeedExpandContent(LayoutWrapper * layoutWrapper,LayoutConstraintF & childConstraint)252 void MenuItemLayoutAlgorithm::CheckNeedExpandContent(LayoutWrapper* layoutWrapper,
253     LayoutConstraintF& childConstraint)
254 {
255     if (needExpandContent_) {
256         auto menuItemNode = layoutWrapper->GetHostNode();
257         CHECK_NULL_VOID(menuItemNode);
258         auto pattern = menuItemNode->GetPattern<MenuItemPattern>();
259         CHECK_NULL_VOID(pattern);
260         auto contentNode = pattern->GetContentNode();
261         CHECK_NULL_VOID(contentNode);
262 
263         auto leftRow = layoutWrapper->GetChildByIndex(0);
264         CHECK_NULL_VOID(leftRow);
265         auto newRowSize = leftRow->GetGeometryNode()->GetFrameSize();
266         newRowSize.SetWidth(emptyWidth_ + newRowSize.Width());
267         leftRow->GetGeometryNode()->SetFrameSize(newRowSize);
268 
269         auto oldTextSize = contentNode->GetGeometryNode()->GetFrameSize();
270         float newTextWidth = emptyWidth_ + oldTextSize.Width();
271         childConstraint.minSize.SetWidth(newTextWidth);
272         childConstraint.maxSize.SetWidth(newTextWidth);
273         contentNode->Measure(childConstraint);
274     }
275 }
276 
UpdateSelfSize(LayoutWrapper * layoutWrapper,float width,float itemHeight,float expandableHeight)277 void MenuItemLayoutAlgorithm::UpdateSelfSize(LayoutWrapper* layoutWrapper,
278     float width, float itemHeight, float expandableHeight)
279 {
280     itemHeight += GetDividerStroke(layoutWrapper);
281     auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
282     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
283         auto height = std::max(itemHeight, minItemHeight_);
284         layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(width, height + expandableHeight));
285         if (clickableArea) {
286             clickableArea->GetGeometryNode()->SetFrameSize(SizeF(width, height));
287         }
288     } else {
289         layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(width, itemHeight));
290     }
291     BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
292 }
293 
GetDividerStroke(LayoutWrapper * layoutWrapper)294 float MenuItemLayoutAlgorithm::GetDividerStroke(LayoutWrapper* layoutWrapper)
295 {
296     auto menuItemNode = layoutWrapper->GetHostNode();
297     CHECK_NULL_RETURN(menuItemNode, 0.0f);
298     auto pattern = menuItemNode->GetPattern<MenuItemPattern>();
299     CHECK_NULL_RETURN(pattern, 0.0f);
300     return pattern->GetDividerStroke();
301 }
302 
GetBordersHeight(LayoutWrapper * layoutWrapper)303 float MenuItemLayoutAlgorithm::GetBordersHeight(LayoutWrapper* layoutWrapper)
304 {
305     auto props = layoutWrapper->GetLayoutProperty();
306     CHECK_NULL_RETURN(props, 0.0f);
307     const auto& border = props->GetBorderWidthProperty();
308     CHECK_NULL_RETURN(border, 0.0f);
309     return border->topDimen.value_or(Dimension(0.0)).ConvertToPx() +
310         border->bottomDimen.value_or(Dimension(0.0)).ConvertToPx();
311 }
312 
GetMenuItemVerticalPadding()313 float MenuItemLayoutAlgorithm::GetMenuItemVerticalPadding()
314 {
315     float ret = 0.0f;
316     auto pipeline = PipelineBase::GetCurrentContext();
317     CHECK_NULL_RETURN(pipeline, ret);
318     auto theme = pipeline->GetTheme<SelectTheme>();
319     CHECK_NULL_RETURN(theme, ret);
320     return theme->GetMenuItemVerticalPadding().ConvertToPx();
321 }
322 } // namespace OHOS::Ace::NG
323