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