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