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_group/menu_item_group_layout_algorithm.h"
17
18 #include "base/memory/ace_type.h"
19 #include "base/utils/utils.h"
20 #include "core/components/select/select_theme.h"
21 #include "core/components_ng/base/frame_node.h"
22 #include "core/components_ng/pattern/menu/menu_item_group/menu_item_group_paint_property.h"
23 #include "core/components_ng/pattern/menu/menu_item_group/menu_item_group_pattern.h"
24 #include "core/components_ng/pattern/menu/menu_pattern.h"
25 #include "core/components_ng/pattern/menu/multi_menu_layout_algorithm.h"
26 #include "core/components_ng/property/calc_length.h"
27 #include "core/components_ng/property/measure_property.h"
28 #include "core/components_ng/property/measure_utils.h"
29 #include "core/components_v2/inspector/inspector_constants.h"
30
31 namespace OHOS::Ace::NG {
Measure(LayoutWrapper * layoutWrapper)32 void MenuItemGroupLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
33 {
34 auto host = layoutWrapper->GetHostNode();
35 CHECK_NULL_VOID(host);
36
37 auto pipeline = PipelineBase::GetCurrentContext();
38 CHECK_NULL_VOID(pipeline);
39 auto theme = pipeline->GetTheme<SelectTheme>();
40 CHECK_NULL_VOID(theme);
41 groupDividerPadding_ = static_cast<float>(theme->GetDividerPaddingVertical().ConvertToPx()) * 2 +
42 static_cast<float>(theme->GetDefaultDividerWidth().ConvertToPx());
43
44 const auto& props = layoutWrapper->GetLayoutProperty();
45 CHECK_NULL_VOID(props);
46 auto layoutConstraint = props->GetLayoutConstraint();
47 CHECK_NULL_VOID(layoutConstraint);
48
49 auto childConstraint = props->CreateChildConstraint();
50 childConstraint.minSize = layoutConstraint->minSize;
51
52 if (layoutConstraint->selfIdealSize.Width().has_value()) {
53 childConstraint.selfIdealSize.SetWidth(layoutConstraint->selfIdealSize.Width().value());
54 }
55 UpdateHeaderAndFooterMargin(layoutWrapper);
56
57 // measure children (header, footer, menuItem)
58 float maxChildrenWidth = GetChildrenMaxWidth(layoutWrapper->GetAllChildrenWithBuild(), childConstraint);
59 SizeF menuItemGroupSize;
60 menuItemGroupSize.SetWidth(maxChildrenWidth);
61 float totalHeight = 0.0f;
62
63 auto minItemHeight = static_cast<float>(theme->GetOptionMinHeight().ConvertToPx());
64
65 // measure header
66 needHeaderPadding_ = NeedHeaderPadding(host);
67 auto paintProperty = host->GetPaintProperty<MenuItemGroupPaintProperty>();
68 CHECK_NULL_VOID(paintProperty);
69 paintProperty->UpdateNeedHeaderPadding(needHeaderPadding_);
70 float headerPadding = needHeaderPadding_ ? groupDividerPadding_ : 0.0f;
71 totalHeight += headerPadding;
72 if (headerIndex_ >= 0) {
73 auto headerWrapper = layoutWrapper->GetOrCreateChildByIndex(headerIndex_);
74 auto headerHeight = headerWrapper->GetGeometryNode()->GetMarginFrameSize().Height();
75 totalHeight += (minItemHeight > headerHeight) ? minItemHeight : headerHeight;
76 }
77 // measure menu item
78 auto totalItemCount = layoutWrapper->GetTotalChildCount();
79 int32_t currentIndex = itemStartIndex_;
80 while (currentIndex < totalItemCount) {
81 auto item = layoutWrapper->GetOrCreateChildByIndex(currentIndex);
82 auto childSize = item->GetGeometryNode()->GetMarginFrameSize();
83 // set minimum size
84 childSize.SetWidth(maxChildrenWidth);
85 MinusPaddingToSize(item->GetLayoutProperty()->CreateMargin(), childSize);
86 if (item->GetLayoutProperty()->GetLayoutConstraint().has_value() &&
87 !item->GetLayoutProperty()->GetLayoutConstraint()->selfIdealSize.Width().has_value()) {
88 item->GetGeometryNode()->SetFrameSize(childSize);
89 }
90
91 float itemHeight = childSize.Height();
92 float endPos = totalHeight + itemHeight;
93 itemPosition_[currentIndex] = { totalHeight, endPos };
94 totalHeight = endPos;
95 ++currentIndex;
96 }
97
98 if (footerIndex_ >= 0) {
99 auto footerWrapper = layoutWrapper->GetOrCreateChildByIndex(footerIndex_);
100 auto footerHeight = footerWrapper->GetGeometryNode()->GetMarginFrameSize().Height();
101 totalHeight += (minItemHeight > footerHeight) ? minItemHeight : footerHeight;
102 }
103 // set menu size
104 needFooterPadding_ = NeedFooterPadding(host);
105 paintProperty->UpdateNeedFooterPadding(needFooterPadding_);
106 float footerPadding = needFooterPadding_ ? groupDividerPadding_ : 0.0f;
107 totalHeight += footerPadding;
108 menuItemGroupSize.SetHeight(totalHeight);
109
110 LOGD("finish measure, menuItemGroup size = %{public}f x %{public}f", menuItemGroupSize.Width(),
111 menuItemGroupSize.Height());
112 layoutWrapper->GetGeometryNode()->SetFrameSize(menuItemGroupSize);
113 }
114
Layout(LayoutWrapper * layoutWrapper)115 void MenuItemGroupLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
116 {
117 const auto& layoutProperty = layoutWrapper->GetLayoutProperty();
118 CHECK_NULL_VOID(layoutProperty);
119 if (headerIndex_ >= 0) {
120 LayoutHeader(layoutWrapper);
121 }
122 if (footerIndex_ >= 0) {
123 LayoutFooter(layoutWrapper);
124 }
125 LayoutMenuItem(layoutWrapper);
126 }
127
LayoutMenuItem(LayoutWrapper * layoutWrapper)128 void MenuItemGroupLayoutAlgorithm::LayoutMenuItem(LayoutWrapper* layoutWrapper)
129 {
130 // layout items.
131 for (auto& pos : itemPosition_) {
132 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(pos.first);
133 if (!wrapper) {
134 LOGI("wrapper is out of boundary");
135 continue;
136 }
137 LayoutIndex(wrapper, OffsetF(0.0, pos.second.first));
138 }
139 }
140
LayoutHeader(LayoutWrapper * layoutWrapper)141 void MenuItemGroupLayoutAlgorithm::LayoutHeader(LayoutWrapper* layoutWrapper)
142 {
143 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(headerIndex_);
144 CHECK_NULL_VOID(wrapper);
145
146 auto pipeline = PipelineBase::GetCurrentContext();
147 CHECK_NULL_VOID(pipeline);
148 auto theme = pipeline->GetTheme<SelectTheme>();
149 CHECK_NULL_VOID(theme);
150 auto headerHeight = wrapper->GetGeometryNode()->GetFrameSize().Height();
151 auto minItemHeight = static_cast<float>(theme->GetOptionMinHeight().ConvertToPx());
152 float headerPadding = (needHeaderPadding_ ? groupDividerPadding_ : 0.0f) +
153 (headerHeight < minItemHeight ? (minItemHeight - headerHeight) / 2 : 0.0f);
154 LayoutIndex(wrapper, OffsetF(0.0f, headerPadding));
155 }
156
LayoutFooter(LayoutWrapper * layoutWrapper)157 void MenuItemGroupLayoutAlgorithm::LayoutFooter(LayoutWrapper* layoutWrapper)
158 {
159 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(footerIndex_);
160 CHECK_NULL_VOID(wrapper);
161 auto footerMainSize = wrapper->GetGeometryNode()->GetFrameSize();
162 auto footerHeight = footerMainSize.Height();
163
164 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
165 auto groupHeight = size.Height();
166
167 auto pipeline = PipelineBase::GetCurrentContext();
168 CHECK_NULL_VOID(pipeline);
169 auto theme = pipeline->GetTheme<SelectTheme>();
170 CHECK_NULL_VOID(theme);
171
172 auto minItemHeight = static_cast<float>(theme->GetOptionMinHeight().ConvertToPx());
173 float footerPadding = (needFooterPadding_ ? groupDividerPadding_ : 0.0f) +
174 (footerHeight < minItemHeight ? (minItemHeight - footerHeight) / 2 : 0.0f);
175 LayoutIndex(wrapper, OffsetF(0.0f, (groupHeight - footerHeight - footerPadding)));
176 }
177
LayoutIndex(const RefPtr<LayoutWrapper> & wrapper,const OffsetF & offset)178 void MenuItemGroupLayoutAlgorithm::LayoutIndex(const RefPtr<LayoutWrapper>& wrapper, const OffsetF& offset)
179 {
180 CHECK_NULL_VOID_NOLOG(wrapper);
181 wrapper->GetGeometryNode()->SetMarginFrameOffset(offset);
182 wrapper->Layout();
183 }
184
185 // Need head padding if left brother is menu item group
NeedHeaderPadding(const RefPtr<FrameNode> & host)186 bool MenuItemGroupLayoutAlgorithm::NeedHeaderPadding(const RefPtr<FrameNode>& host)
187 {
188 auto brotherNode = GetBrotherNode(host);
189 CHECK_NULL_RETURN_NOLOG(brotherNode, false);
190 return brotherNode->GetTag() != V2::MENU_ITEM_GROUP_ETS_TAG;
191 }
192
NeedFooterPadding(const RefPtr<FrameNode> & host)193 bool MenuItemGroupLayoutAlgorithm::NeedFooterPadding(const RefPtr<FrameNode>& host)
194 {
195 return !IsLastNode(host);
196 }
197
GetChildrenMaxWidth(const std::list<RefPtr<LayoutWrapper>> & children,const LayoutConstraintF & layoutConstraint)198 float MenuItemGroupLayoutAlgorithm::GetChildrenMaxWidth(
199 const std::list<RefPtr<LayoutWrapper>>& children, const LayoutConstraintF& layoutConstraint)
200 {
201 float width = layoutConstraint.minSize.Width();
202
203 for (const auto& child : children) {
204 child->Measure(MultiMenuLayoutAlgorithm::ResetLayoutConstraintMinWidth(child, layoutConstraint));
205 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
206 width = std::max(width, childSize.Width());
207 }
208 return width;
209 }
210
GetItemsAndGroups(const RefPtr<FrameNode> & host) const211 std::list<WeakPtr<UINode>> MenuItemGroupLayoutAlgorithm::GetItemsAndGroups(const RefPtr<FrameNode>& host) const
212 {
213 std::list<WeakPtr<UINode>> itemsAndGroups;
214 auto pattern = host->GetPattern<MenuItemGroupPattern>();
215 CHECK_NULL_RETURN(pattern, itemsAndGroups);
216 auto menu = pattern->GetMenu();
217 CHECK_NULL_RETURN(menu, itemsAndGroups);
218 auto menuPattern = menu->GetPattern<InnerMenuPattern>();
219 CHECK_NULL_RETURN(menuPattern, itemsAndGroups);
220 return menuPattern->GetItemsAndGroups();
221 }
222
223 // get the left brother node
GetBrotherNode(const RefPtr<FrameNode> & host)224 RefPtr<FrameNode> MenuItemGroupLayoutAlgorithm::GetBrotherNode(const RefPtr<FrameNode>& host)
225 {
226 auto itemsAndGroups = GetItemsAndGroups(host);
227 if (itemsAndGroups.empty()) {
228 return nullptr;
229 }
230 auto iter = std::find(itemsAndGroups.begin(), itemsAndGroups.end(), host);
231 if (iter == itemsAndGroups.begin() || iter == itemsAndGroups.end()) {
232 return nullptr;
233 }
234 return DynamicCast<FrameNode>((--iter)->Upgrade());
235 }
236
IsLastNode(const RefPtr<FrameNode> & host) const237 bool MenuItemGroupLayoutAlgorithm::IsLastNode(const RefPtr<FrameNode>& host) const
238 {
239 auto itemsAndGroups = GetItemsAndGroups(host);
240 if (itemsAndGroups.empty()) {
241 return true;
242 }
243 return host == itemsAndGroups.back().Upgrade();
244 }
245
UpdateHeaderAndFooterMargin(LayoutWrapper * layoutWrapper) const246 void MenuItemGroupLayoutAlgorithm::UpdateHeaderAndFooterMargin(LayoutWrapper* layoutWrapper) const
247 {
248 if (headerIndex_ < 0 && footerIndex_ < 0) {
249 // no header and footer, no need to update.
250 return;
251 }
252 auto host = layoutWrapper->GetHostNode();
253 auto pattern = host->GetPattern<MenuItemGroupPattern>();
254 pattern->UpdateMenuItemIconInfo();
255
256 auto pipeline = PipelineBase::GetCurrentContext();
257 CHECK_NULL_VOID(pipeline);
258 auto selectTheme = pipeline->GetTheme<SelectTheme>();
259 CHECK_NULL_VOID(selectTheme);
260 auto iconWidth = selectTheme->GetIconSideLength();
261 auto iconContentPadding = selectTheme->GetIconContentPadding();
262 auto margin = MarginProperty();
263 if (pattern->HasSelectIcon() && pattern->HasStartIcon()) {
264 margin.left = CalcLength(iconWidth * 2.0 + iconContentPadding * 2.0);
265 } else if (pattern->HasSelectIcon() || pattern->HasStartIcon()) {
266 margin.left = CalcLength(iconWidth + iconContentPadding);
267 } else {
268 // no need to update zero margin.
269 return;
270 }
271
272 if (headerIndex_ >= 0) {
273 auto headerWrapper = layoutWrapper->GetOrCreateChildByIndex(headerIndex_);
274 auto headLayoutProps = headerWrapper->GetLayoutProperty();
275 headLayoutProps->UpdateMargin(margin);
276 }
277 if (footerIndex_ >= 0) {
278 auto footerWrapper = layoutWrapper->GetOrCreateChildByIndex(footerIndex_);
279 auto footerLayoutProps = footerWrapper->GetLayoutProperty();
280 footerLayoutProps->UpdateMargin(margin);
281 }
282 }
283 } // namespace OHOS::Ace::NG
284