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