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