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