1 /*
2  * Copyright (c) 2022 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/button/button_layout_algorithm.h"
17 
18 #include "base/utils/utils.h"
19 #include "core/components/button/button_theme.h"
20 #include "core/components/toggle/toggle_theme.h"
21 #include "core/components_ng/base/frame_node.h"
22 #include "core/components_ng/layout/layout_wrapper.h"
23 #include "core/components_ng/pattern/button/button_layout_property.h"
24 #include "core/components_ng/pattern/text/text_layout_property.h"
25 #include "core/components_ng/property/measure_utils.h"
26 #include "core/pipeline_ng/pipeline_context.h"
27 #include "core/components_ng/pattern/button/button_pattern.h"
28 
29 namespace OHOS::Ace::NG {
30 
Measure(LayoutWrapper * layoutWrapper)31 void ButtonLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
32 {
33     auto host = layoutWrapper->GetHostNode();
34     CHECK_NULL_VOID(host);
35     auto pattern = host->GetPattern<ButtonPattern>();
36     CHECK_NULL_VOID(pattern);
37     if (pattern->UseContentModifier()) {
38         const auto& childList = layoutWrapper->GetAllChildrenWithBuild();
39         std::list<RefPtr<LayoutWrapper>> builderChildList;
40         for (const auto& child : childList) {
41             if (child->GetHostNode()->GetId() != pattern->GetBuilderId()) {
42                 child->GetGeometryNode()->Reset();
43                 child->GetGeometryNode()->SetContentSize(SizeF());
44             } else {
45                 auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
46                 child->Measure(layoutConstraint);
47                 builderChildList.push_back(child);
48             }
49         }
50         BoxLayoutAlgorithm::PerformMeasureSelfWithChildList(layoutWrapper, builderChildList);
51         return;
52     }
53     auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
54     HandleChildLayoutConstraint(layoutWrapper, layoutConstraint);
55     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
56     CHECK_NULL_VOID(buttonLayoutProperty);
57     if (buttonLayoutProperty->HasLabel()) {
58         // If the button has label, according to whether the font size is set to do the corresponding expansion button,
59         // font reduction, truncation and other operations.
60         HandleAdaptiveText(layoutWrapper, layoutConstraint);
61     } else {
62         // If the button has not label, measure the child directly.
63         for (auto&& child : layoutWrapper->GetAllChildrenWithBuild()) {
64             child->Measure(layoutConstraint);
65         }
66     }
67     PerformMeasureSelf(layoutWrapper);
68     MarkNeedFlushMouseEvent(layoutWrapper);
69 }
70 
HandleChildLayoutConstraint(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)71 void ButtonLayoutAlgorithm::HandleChildLayoutConstraint(
72     LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
73 {
74     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
75     CHECK_NULL_VOID(buttonLayoutProperty);
76     if (!buttonLayoutProperty->HasLabel()) {
77         return;
78     }
79     if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
80         layoutConstraint.maxSize = HandleLabelCircleButtonConstraint(layoutWrapper).value_or(SizeF());
81         return;
82     }
83     const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
84     // If height is not set, apply the default height.
85     if (selfLayoutConstraint && !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
86         auto maxHeight = selfLayoutConstraint->maxSize.Height();
87         if (IsAging(layoutWrapper)) {
88             layoutConstraint.maxSize.SetHeight(maxHeight);
89             return;
90         }
91         auto defaultHeight = GetDefaultHeight(layoutWrapper);
92         layoutConstraint.maxSize.SetHeight(maxHeight > defaultHeight ? defaultHeight : maxHeight);
93     }
94 }
95 
96 // If the ButtonType is CIRCLE, then omit text by the smaller edge.
HandleLabelCircleButtonConstraint(LayoutWrapper * layoutWrapper)97 std::optional<SizeF> ButtonLayoutAlgorithm::HandleLabelCircleButtonConstraint(LayoutWrapper* layoutWrapper)
98 {
99     SizeF constraintSize;
100     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
101     CHECK_NULL_RETURN(buttonLayoutProperty, constraintSize);
102     const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
103     CHECK_NULL_RETURN(selfLayoutConstraint, constraintSize);
104     auto host = layoutWrapper->GetHostNode();
105     CHECK_NULL_RETURN(host, constraintSize);
106     auto* context = host->GetContextWithCheck();
107     CHECK_NULL_RETURN(context, constraintSize);
108     auto buttonTheme = context->GetTheme<ButtonTheme>();
109     CHECK_NULL_RETURN(buttonTheme, constraintSize);
110     const auto& padding = buttonLayoutProperty->CreatePaddingAndBorder();
111     auto defaultHeight = GetDefaultHeight(layoutWrapper);
112     float minLength = 0.0f;
113     if (selfLayoutConstraint->selfIdealSize.IsNull()) {
114         // Width and height are not set.
115         minLength = defaultHeight;
116     } else if (selfLayoutConstraint->selfIdealSize.Width().has_value() &&
117                !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
118         // Only width is set.
119         minLength = selfLayoutConstraint->selfIdealSize.Width().value();
120     } else if (selfLayoutConstraint->selfIdealSize.Height().has_value() &&
121                !selfLayoutConstraint->selfIdealSize.Width().has_value()) {
122         // Only height is set.
123         minLength = selfLayoutConstraint->selfIdealSize.Height().value();
124     } else {
125         // Both width and height are set.
126         auto buttonWidth = selfLayoutConstraint->selfIdealSize.Width().value();
127         auto buttonHeight = selfLayoutConstraint->selfIdealSize.Height().value();
128         minLength = std::min(buttonWidth, buttonHeight);
129     }
130     if (buttonLayoutProperty->HasBorderRadius() && selfLayoutConstraint->selfIdealSize.IsNull()) {
131         auto radius =
132             static_cast<float>(GetFirstValidRadius(buttonLayoutProperty->GetBorderRadius().value()).ConvertToPx());
133         minLength = 2 * radius;
134     }
135     constraintSize.SetSizeT(SizeF(minLength, minLength));
136     MinusPaddingToSize(padding, constraintSize);
137     return ConstrainSize(constraintSize, selfLayoutConstraint->minSize, selfLayoutConstraint->maxSize);
138 }
139 
HandleAdaptiveText(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)140 void ButtonLayoutAlgorithm::HandleAdaptiveText(LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
141 {
142     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
143     CHECK_NULL_VOID(buttonLayoutProperty);
144     auto host = layoutWrapper->GetHostNode();
145     CHECK_NULL_VOID(host);
146     auto* context = host->GetContextWithCheck();
147     CHECK_NULL_VOID(context);
148     auto buttonTheme = context->GetTheme<ButtonTheme>();
149     CHECK_NULL_VOID(buttonTheme);
150     auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
151     CHECK_NULL_VOID(childWrapper);
152     auto childConstraint = layoutWrapper->GetLayoutProperty()->GetContentLayoutConstraint();
153     childWrapper->Measure(childConstraint);
154     auto textSize = childWrapper->GetGeometryNode()->GetContentSize();
155     if (buttonLayoutProperty->HasFontSize() || buttonLayoutProperty->HasControlSize()) {
156         // Fonsize is set. When the font height is larger than the button height, make the button fit the font
157         // height.
158         if (GreatOrEqual(textSize.Height(), layoutConstraint.maxSize.Height())) {
159             layoutConstraint.maxSize.SetHeight(textSize.Height());
160         }
161     } else {
162         // Fonsize is not set. When the font width is greater than the button width, dynamically change the font
163         // size to no less than 9sp.
164         auto textLayoutProperty = DynamicCast<TextLayoutProperty>(childWrapper->GetLayoutProperty());
165         textLayoutProperty->UpdateAdaptMaxFontSize(
166             buttonLayoutProperty->GetMaxFontSize().value_or(buttonTheme->GetMaxFontSize()));
167         textLayoutProperty->UpdateAdaptMinFontSize(
168             buttonLayoutProperty->GetMinFontSize().value_or(buttonTheme->GetMinFontSize()));
169     }
170     childWrapper->Measure(layoutConstraint);
171     childSize_ = childWrapper->GetGeometryNode()->GetContentSize();
172 }
173 
HandleBorderRadius(LayoutWrapper * layoutWrapper)174 void ButtonLayoutAlgorithm::HandleBorderRadius(LayoutWrapper* layoutWrapper)
175 {
176     auto host = layoutWrapper->GetHostNode();
177     CHECK_NULL_VOID(host);
178     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
179     CHECK_NULL_VOID(buttonLayoutProperty);
180     auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
181     auto renderContext = host->GetRenderContext();
182     if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
183         auto minSize = std::min(frameSize.Height(), frameSize.Width());
184         auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
185         if (buttonLayoutProperty->HasBorderRadius() && layoutConstraint.parentIdealSize.IsNull()) {
186             auto borderRadius = buttonLayoutProperty->GetBorderRadius().value_or(NG::BorderRadiusProperty());
187             minSize = static_cast<float>(GetFirstValidRadius(borderRadius).ConvertToPx() * 2);
188         }
189         renderContext->UpdateBorderRadius(BorderRadiusProperty(Dimension(minSize / 2)));
190         MeasureCircleButton(layoutWrapper);
191     } else if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::NORMAL) {
192         auto normalRadius =
193             buttonLayoutProperty->GetBorderRadiusValue(BorderRadiusProperty({ 0.0_vp, 0.0_vp, 0.0_vp, 0.0_vp }));
194         renderContext->UpdateBorderRadius(normalRadius);
195     } else {
196         renderContext->UpdateBorderRadius(BorderRadiusProperty(Dimension(frameSize.Height() / 2)));
197     }
198 }
199 
200 // Called to perform measure current render node.
PerformMeasureSelf(LayoutWrapper * layoutWrapper)201 void ButtonLayoutAlgorithm::PerformMeasureSelf(LayoutWrapper* layoutWrapper)
202 {
203     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
204     CHECK_NULL_VOID(buttonLayoutProperty);
205     BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
206     if (NeedAgingMeasure(layoutWrapper)) {
207         return;
208     }
209     if (buttonLayoutProperty->HasLabel()) {
210         auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
211         auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
212         const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
213         auto padding = buttonLayoutProperty->CreatePaddingAndBorder();
214         auto topPadding = padding.top.value_or(0.0);
215         auto bottomPadding = padding.bottom.value_or(0.0);
216         auto host = layoutWrapper->GetHostNode();
217         CHECK_NULL_VOID(host);
218         auto* context = host->GetContextWithCheck();
219         CHECK_NULL_VOID(context);
220         auto buttonTheme = context->GetTheme<ButtonTheme>();
221         CHECK_NULL_VOID(buttonTheme);
222         auto defaultHeight = GetDefaultHeight(layoutWrapper);
223         if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
224             HandleLabelCircleButtonFrameSize(layoutConstraint, frameSize, defaultHeight);
225         } else {
226             if (selfLayoutConstraint && !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
227                 auto layoutContraint = buttonLayoutProperty->GetLayoutConstraint();
228                 CHECK_NULL_VOID(layoutContraint);
229                 auto maxHeight = layoutContraint->maxSize.Height();
230                 auto minHeight = layoutContraint->minSize.Height();
231                 auto actualHeight = static_cast<float>(childSize_.Height() + topPadding + bottomPadding);
232                 actualHeight = std::min(actualHeight, maxHeight);
233                 actualHeight = std::max(actualHeight, minHeight);
234                 frameSize.SetHeight(maxHeight > defaultHeight ? std::max(defaultHeight, actualHeight) : maxHeight);
235             }
236         }
237         // Determine if the button needs to fit the font size.
238         if (buttonLayoutProperty->HasFontSize()) {
239             if (GreatOrEqual(childSize_.Height() + topPadding + bottomPadding, frameSize.Height())) {
240                 frameSize = SizeF(frameSize.Width(), childSize_.Height() + topPadding + bottomPadding);
241             }
242         }
243         layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
244     }
245     HandleBorderRadius(layoutWrapper);
246 }
247 
HandleLabelCircleButtonFrameSize(const LayoutConstraintF & layoutConstraint,SizeF & frameSize,const float & defaultHeight)248 void ButtonLayoutAlgorithm::HandleLabelCircleButtonFrameSize(
249     const LayoutConstraintF& layoutConstraint, SizeF& frameSize, const float& defaultHeight)
250 {
251     float minLength = 0.0f;
252     if (layoutConstraint.parentIdealSize.IsNull()) {
253         minLength = static_cast<float>(defaultHeight);
254     } else if (layoutConstraint.parentIdealSize.Width().has_value() &&
255                !layoutConstraint.parentIdealSize.Height().has_value()) {
256         minLength = frameSize.Width();
257     } else if (layoutConstraint.parentIdealSize.Height().has_value() &&
258                !layoutConstraint.parentIdealSize.Width().has_value()) {
259         minLength = frameSize.Height();
260     } else {
261         minLength = std::min(frameSize.Width(), frameSize.Height());
262     }
263     frameSize.SetWidth(minLength);
264     frameSize.SetHeight(minLength);
265 }
266 
MeasureCircleButton(LayoutWrapper * layoutWrapper)267 void ButtonLayoutAlgorithm::MeasureCircleButton(LayoutWrapper* layoutWrapper)
268 {
269     auto frameNode = layoutWrapper->GetHostNode();
270     CHECK_NULL_VOID(frameNode);
271     const auto& radius = frameNode->GetRenderContext()->GetBorderRadius();
272     SizeF frameSize = { -1, -1 };
273     if (radius.has_value()) {
274         auto radiusTopMax = std::max(radius->radiusTopLeft, radius->radiusTopRight);
275         auto radiusBottomMax = std::max(radius->radiusBottomLeft, radius->radiusBottomRight);
276         auto radiusMax = std::max(radiusTopMax, radiusBottomMax);
277         auto rrectRadius = radiusMax.value_or(0.0_vp).ConvertToPx();
278         frameSize.SetSizeT(SizeF { static_cast<float>(rrectRadius * 2), static_cast<float>(rrectRadius * 2) });
279     }
280     frameSize.UpdateIllegalSizeWithCheck(SizeF { 0.0f, 0.0f });
281     layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
282 }
283 
GetFirstValidRadius(const BorderRadiusProperty & borderRadius)284 Dimension ButtonLayoutAlgorithm::GetFirstValidRadius(const BorderRadiusProperty& borderRadius)
285 {
286     if (borderRadius.radiusTopLeft.has_value()) {
287         return borderRadius.radiusTopLeft.value();
288     }
289     if (borderRadius.radiusTopRight.has_value()) {
290         return borderRadius.radiusTopRight.value();
291     }
292     if (borderRadius.radiusBottomLeft.has_value()) {
293         return borderRadius.radiusBottomLeft.value();
294     }
295     if (borderRadius.radiusBottomRight.has_value()) {
296         return borderRadius.radiusBottomRight.value();
297     }
298     return 0.0_vp;
299 }
300 
GetDefaultHeight(LayoutWrapper * layoutWrapper)301 float ButtonLayoutAlgorithm::GetDefaultHeight(LayoutWrapper* layoutWrapper)
302 {
303     auto layoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
304     CHECK_NULL_RETURN(layoutProperty, 0.0);
305     auto frameNode = layoutWrapper->GetHostNode();
306     CHECK_NULL_RETURN(frameNode, 0.0);
307     auto* context = frameNode->GetContext();
308     CHECK_NULL_RETURN(context, 0.0);
309     auto buttonTheme = context->GetTheme<ButtonTheme>();
310     CHECK_NULL_RETURN(buttonTheme, 0.0);
311     if (frameNode->GetTag() == V2::TOGGLE_ETS_TAG) {
312         auto toggleTheme = context->GetTheme<ToggleTheme>();
313         CHECK_NULL_RETURN(toggleTheme, 0.0);
314         return static_cast<float>(toggleTheme->GetButtonHeight().ConvertToPx());
315     }
316     ControlSize controlSize = layoutProperty->GetControlSize().value_or(ControlSize::NORMAL);
317     return static_cast<float>(buttonTheme->GetHeight(controlSize).ConvertToPx());
318 }
319 
MarkNeedFlushMouseEvent(LayoutWrapper * layoutWrapper)320 void ButtonLayoutAlgorithm::MarkNeedFlushMouseEvent(LayoutWrapper* layoutWrapper)
321 {
322     auto host = layoutWrapper->GetHostNode();
323     CHECK_NULL_VOID(host);
324     auto pattern = host->GetPattern<ButtonPattern>();
325     CHECK_NULL_VOID(pattern);
326     auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
327     if (frameSize != pattern->GetPreFrameSize()) {
328         pattern->SetPreFrameSize(frameSize);
329         auto context = PipelineContext::GetCurrentContext();
330         CHECK_NULL_VOID(context);
331         context->MarkNeedFlushMouseEvent();
332     }
333 }
334 
NeedAgingMeasure(LayoutWrapper * layoutWrapper)335 bool ButtonLayoutAlgorithm::NeedAgingMeasure(LayoutWrapper* layoutWrapper)
336 {
337     if (!IsAging(layoutWrapper)) {
338         return false;
339     }
340     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
341     CHECK_NULL_RETURN(buttonLayoutProperty, false);
342     auto pipeline = NG::PipelineContext::GetCurrentContextSafely();
343     CHECK_NULL_RETURN(pipeline, false);
344     auto buttonTheme = pipeline->GetTheme<ButtonTheme>();
345     float agingPadding = buttonTheme->GetAgingNormalPadding().ConvertToPx() * 2.0f;
346     if (buttonLayoutProperty->HasControlSize() && buttonLayoutProperty->GetControlSize() == ControlSize::SMALL) {
347         agingPadding = buttonTheme->GetAgingSmallPadding().ConvertToPx() * 2.0f;
348     }
349     auto host = layoutWrapper->GetHostNode();
350     CHECK_NULL_RETURN(host, false);
351     auto pattern = host->GetPattern<ButtonPattern>();
352     CHECK_NULL_RETURN(pattern, false);
353     if (!pattern->GetHasCustomPadding()) {
354         auto geometryNode = layoutWrapper->GetGeometryNode();
355         CHECK_NULL_RETURN(geometryNode, false);
356         auto frameSize = geometryNode->GetFrameSize();
357         if (buttonLayoutProperty->HasLabel()) {
358             auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
359             CHECK_NULL_RETURN(childWrapper, false);
360             auto childGeometryNode = childWrapper->GetGeometryNode();
361             CHECK_NULL_RETURN(childGeometryNode, false);
362             auto childFrameSize = childGeometryNode->GetContentSize();
363             frameSize.SetHeight(childFrameSize.Height() + agingPadding);
364         } else {
365             frameSize.SetHeight(frameSize.Height() + agingPadding);
366         }
367         geometryNode->SetFrameSize(frameSize);
368     }
369     HandleBorderRadius(layoutWrapper);
370     return true;
371 }
372 
IsAging(LayoutWrapper * layoutWrapper)373 bool ButtonLayoutAlgorithm::IsAging(LayoutWrapper* layoutWrapper)
374 {
375     auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
376     CHECK_NULL_RETURN(buttonLayoutProperty, false);
377 
378     if (buttonLayoutProperty->HasType() && buttonLayoutProperty->GetType() == ButtonType::CIRCLE) {
379         return false;
380     }
381 
382     if (buttonLayoutProperty->HasLabel() && buttonLayoutProperty->GetLabel()->empty()) {
383         return false;
384     }
385 
386     if (buttonLayoutProperty->HasFontSize() && buttonLayoutProperty->GetFontSize()->Unit() != DimensionUnit::FP) {
387         return false;
388     }
389     const auto& calcConstraint = buttonLayoutProperty->GetCalcLayoutConstraint();
390     if (calcConstraint && calcConstraint->selfIdealSize->Height().has_value() &&
391         calcConstraint->selfIdealSize->Width().has_value()) {
392         return false;
393     }
394     auto pipeline = NG::PipelineContext::GetCurrentContextSafely();
395     CHECK_NULL_RETURN(pipeline, false);
396     auto buttonTheme = pipeline->GetTheme<ButtonTheme>();
397     CHECK_NULL_RETURN(buttonTheme, false);
398     auto fontScale = pipeline->GetFontScale();
399     if (!(NearEqual(fontScale, buttonTheme->GetBigFontSizeScale()) ||
400             NearEqual(fontScale, buttonTheme->GetLargeFontSizeScale()) ||
401             NearEqual(fontScale, buttonTheme->GetMaxFontSizeScale()))) {
402         return false;
403     }
404     return true;
405 }
406 } // namespace OHOS::Ace::NG
407