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_ng/base/frame_node.h"
21 #include "core/components_ng/layout/layout_wrapper.h"
22 #include "core/components_ng/pattern/button/button_layout_property.h"
23 #include "core/components_ng/pattern/text/text_layout_property.h"
24 #include "core/components_ng/property/measure_utils.h"
25 #include "core/pipeline_ng/pipeline_context.h"
26
27 namespace OHOS::Ace::NG {
28
Measure(LayoutWrapper * layoutWrapper)29 void ButtonLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
30 {
31 auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
32 HandleChildLayoutConstraint(layoutWrapper, layoutConstraint);
33 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
34 CHECK_NULL_VOID(buttonLayoutProperty);
35 if (buttonLayoutProperty->HasLabel()) {
36 // If the button has label, according to whether the font size is set to do the corresponding expansion button,
37 // font reduction, truncation and other operations.
38 HandleAdaptiveText(layoutWrapper, layoutConstraint);
39 } else {
40 // If the button has not label, measure the child directly.
41 for (auto&& child : layoutWrapper->GetAllChildrenWithBuild()) {
42 child->Measure(layoutConstraint);
43 }
44 }
45 PerformMeasureSelf(layoutWrapper);
46 }
47
HandleChildLayoutConstraint(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)48 void ButtonLayoutAlgorithm::HandleChildLayoutConstraint(
49 LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
50 {
51 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
52 CHECK_NULL_VOID(buttonLayoutProperty);
53 if (!buttonLayoutProperty->HasLabel()) {
54 return;
55 }
56 if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
57 layoutConstraint.maxSize = HandleLabelCircleButtonConstraint(layoutWrapper).value_or(SizeF());
58 return;
59 }
60 const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
61 // If height is not set, apply the default height.
62 if (selfLayoutConstraint && !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
63 auto buttonTheme = PipelineBase::GetCurrentContext()->GetTheme<ButtonTheme>();
64 CHECK_NULL_VOID(buttonTheme);
65 auto defaultHeight = static_cast<float>(buttonTheme->GetHeight().ConvertToPx());
66 auto maxHeight = selfLayoutConstraint->maxSize.Height();
67 layoutConstraint.maxSize.SetHeight(maxHeight > defaultHeight ? defaultHeight : maxHeight);
68 }
69 }
70
71 // If the ButtonType is CIRCLE, then omit text by the smaller edge.
HandleLabelCircleButtonConstraint(LayoutWrapper * layoutWrapper)72 std::optional<SizeF> ButtonLayoutAlgorithm::HandleLabelCircleButtonConstraint(LayoutWrapper* layoutWrapper)
73 {
74 SizeF constraintSize;
75 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
76 CHECK_NULL_RETURN(buttonLayoutProperty, constraintSize);
77 const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
78 auto buttonTheme = PipelineBase::GetCurrentContext()->GetTheme<ButtonTheme>();
79 CHECK_NULL_RETURN(buttonTheme, constraintSize);
80 const auto& padding = buttonLayoutProperty->CreatePaddingAndBorder();
81 auto defaultHeight = static_cast<float>(buttonTheme->GetHeight().ConvertToPx());
82 float minLength = 0.0f;
83 if (selfLayoutConstraint->selfIdealSize.IsNull()) {
84 // Width and height are not set.
85 minLength = defaultHeight;
86 } else if (selfLayoutConstraint->selfIdealSize.Width().has_value() &&
87 !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
88 // Only width is set.
89 minLength = selfLayoutConstraint->selfIdealSize.Width().value();
90 } else if (selfLayoutConstraint->selfIdealSize.Height().has_value() &&
91 !selfLayoutConstraint->selfIdealSize.Width().has_value()) {
92 // Only height is set.
93 minLength = selfLayoutConstraint->selfIdealSize.Height().value();
94 } else {
95 // Both width and height are set.
96 auto buttonWidth = selfLayoutConstraint->selfIdealSize.Width().value();
97 auto buttonHeight = selfLayoutConstraint->selfIdealSize.Height().value();
98 minLength = std::min(buttonWidth, buttonHeight);
99 }
100 if (buttonLayoutProperty->HasBorderRadius() && selfLayoutConstraint->selfIdealSize.IsNull()) {
101 auto radius =
102 static_cast<float>(GetFirstValidRadius(buttonLayoutProperty->GetBorderRadius().value()).ConvertToPx());
103 minLength = 2 * radius;
104 }
105 constraintSize.SetSizeT(SizeF(minLength, minLength));
106 MinusPaddingToSize(padding, constraintSize);
107 return ConstrainSize(constraintSize, selfLayoutConstraint->minSize, selfLayoutConstraint->maxSize);
108 }
109
HandleAdaptiveText(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)110 void ButtonLayoutAlgorithm::HandleAdaptiveText(LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
111 {
112 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
113 CHECK_NULL_VOID(buttonLayoutProperty);
114 auto buttonTheme = PipelineBase::GetCurrentContext()->GetTheme<ButtonTheme>();
115 CHECK_NULL_VOID(buttonTheme);
116 auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
117 CHECK_NULL_VOID(childWrapper);
118 auto childConstraint = layoutWrapper->GetLayoutProperty()->GetContentLayoutConstraint();
119 childWrapper->Measure(childConstraint);
120 auto textSize = childWrapper->GetGeometryNode()->GetContentSize();
121 if (buttonLayoutProperty->HasFontSize()) {
122 // Fonsize is set. When the font height is larger than the button height, make the button fit the font
123 // height.
124 if (GreatOrEqual(textSize.Height(), layoutConstraint.maxSize.Height())) {
125 layoutConstraint.maxSize.SetHeight(textSize.Height());
126 }
127 } else {
128 // Fonsize is not set. When the font width is greater than the button width, dynamically change the font
129 // size to no less than 9sp.
130 auto textLayoutProperty = DynamicCast<TextLayoutProperty>(childWrapper->GetLayoutProperty());
131 textLayoutProperty->UpdateAdaptMaxFontSize(
132 buttonLayoutProperty->GetMaxFontSize().value_or(buttonTheme->GetMaxFontSize()));
133 textLayoutProperty->UpdateAdaptMinFontSize(
134 buttonLayoutProperty->GetMinFontSize().value_or(buttonTheme->GetMinFontSize()));
135 }
136 childWrapper->Measure(layoutConstraint);
137 childSize_ = childWrapper->GetGeometryNode()->GetContentSize();
138 }
139
HandleBorderRadius(LayoutWrapper * layoutWrapper)140 void ButtonLayoutAlgorithm::HandleBorderRadius(LayoutWrapper* layoutWrapper)
141 {
142 auto host = layoutWrapper->GetHostNode();
143 CHECK_NULL_VOID(host);
144 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
145 CHECK_NULL_VOID(buttonLayoutProperty);
146 auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
147 auto renderContext = host->GetRenderContext();
148 if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
149 auto minSize = std::min(frameSize.Height(), frameSize.Width());
150 auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
151 if (buttonLayoutProperty->HasBorderRadius() && layoutConstraint.parentIdealSize.IsNull()) {
152 auto borderRadius = buttonLayoutProperty->GetBorderRadius().value_or(NG::BorderRadiusProperty());
153 minSize = static_cast<float>(GetFirstValidRadius(borderRadius).ConvertToPx() * 2);
154 }
155 renderContext->UpdateBorderRadius(BorderRadiusProperty(Dimension(minSize / 2)));
156 MeasureCircleButton(layoutWrapper);
157 } else if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CAPSULE) {
158 renderContext->UpdateBorderRadius(BorderRadiusProperty(Dimension(frameSize.Height() / 2)));
159 } else {
160 auto normalRadius =
161 buttonLayoutProperty->GetBorderRadiusValue(BorderRadiusProperty({ 0.0_vp, 0.0_vp, 0.0_vp, 0.0_vp }));
162 renderContext->UpdateBorderRadius(normalRadius);
163 }
164 }
165
166 // Called to perform measure current render node.
PerformMeasureSelf(LayoutWrapper * layoutWrapper)167 void ButtonLayoutAlgorithm::PerformMeasureSelf(LayoutWrapper* layoutWrapper)
168 {
169 auto buttonLayoutProperty = DynamicCast<ButtonLayoutProperty>(layoutWrapper->GetLayoutProperty());
170 CHECK_NULL_VOID(buttonLayoutProperty);
171 BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
172 if (buttonLayoutProperty->HasLabel()) {
173 auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
174 auto layoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
175 const auto& selfLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
176 auto padding = buttonLayoutProperty->CreatePaddingAndBorder();
177 auto topPadding = padding.top.value_or(0.0);
178 auto bottomPadding = padding.bottom.value_or(0.0);
179 if (buttonLayoutProperty->GetType().value_or(ButtonType::CAPSULE) == ButtonType::CIRCLE) {
180 HandleLabelCircleButtonFrameSize(layoutConstraint, frameSize);
181 } else {
182 if (selfLayoutConstraint && !selfLayoutConstraint->selfIdealSize.Height().has_value()) {
183 auto buttonTheme = PipelineBase::GetCurrentContext()->GetTheme<ButtonTheme>();
184 CHECK_NULL_VOID(buttonTheme);
185 auto defaultHeight = static_cast<float>(buttonTheme->GetHeight().ConvertToPx());
186 auto layoutContraint = buttonLayoutProperty->GetLayoutConstraint();
187 CHECK_NULL_VOID(layoutContraint);
188 auto maxHeight = layoutContraint->maxSize.Height();
189 auto actualHeight = static_cast<float>(childSize_.Height() + topPadding + bottomPadding);
190 actualHeight = std::min(actualHeight, maxHeight);
191 frameSize.SetHeight(maxHeight > defaultHeight ? std::max(defaultHeight, actualHeight) : maxHeight);
192 }
193 }
194 // Determine if the button needs to fit the font size.
195 if (buttonLayoutProperty->HasFontSize()) {
196 if (GreatOrEqual(childSize_.Height() + topPadding + bottomPadding, frameSize.Height())) {
197 frameSize = SizeF(frameSize.Width(), childSize_.Height() + topPadding + bottomPadding);
198 }
199 }
200 layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
201 }
202 HandleBorderRadius(layoutWrapper);
203 }
204
HandleLabelCircleButtonFrameSize(const LayoutConstraintF & layoutConstraint,SizeF & frameSize)205 void ButtonLayoutAlgorithm::HandleLabelCircleButtonFrameSize(
206 const LayoutConstraintF& layoutConstraint, SizeF& frameSize)
207 {
208 auto pipeline = PipelineBase::GetCurrentContext();
209 CHECK_NULL_VOID(pipeline);
210 auto buttonTheme = pipeline->GetTheme<ButtonTheme>();
211 CHECK_NULL_VOID(buttonTheme);
212 auto defaultHeight = buttonTheme->GetHeight().ConvertToPx();
213 float minLength = 0.0f;
214 if (layoutConstraint.parentIdealSize.IsNull()) {
215 minLength = static_cast<float>(defaultHeight);
216 } else if (layoutConstraint.parentIdealSize.Width().has_value() &&
217 !layoutConstraint.parentIdealSize.Height().has_value()) {
218 minLength = frameSize.Width();
219 } else if (layoutConstraint.parentIdealSize.Height().has_value() &&
220 !layoutConstraint.parentIdealSize.Width().has_value()) {
221 minLength = frameSize.Height();
222 } else {
223 minLength = std::min(frameSize.Width(), frameSize.Height());
224 }
225 frameSize.SetWidth(minLength);
226 frameSize.SetHeight(minLength);
227 }
228
MeasureCircleButton(LayoutWrapper * layoutWrapper)229 void ButtonLayoutAlgorithm::MeasureCircleButton(LayoutWrapper* layoutWrapper)
230 {
231 auto frameNode = layoutWrapper->GetHostNode();
232 CHECK_NULL_VOID(frameNode);
233 const auto& radius = frameNode->GetRenderContext()->GetBorderRadius();
234 SizeF frameSize = { -1, -1 };
235 if (radius.has_value()) {
236 auto radiusTopMax = std::max(radius->radiusTopLeft, radius->radiusTopRight);
237 auto radiusBottomMax = std::max(radius->radiusBottomLeft, radius->radiusBottomRight);
238 auto radiusMax = std::max(radiusTopMax, radiusBottomMax);
239 auto rrectRadius = radiusMax.value_or(0.0_vp).ConvertToPx();
240 frameSize.SetSizeT(SizeF { static_cast<float>(rrectRadius * 2), static_cast<float>(rrectRadius * 2) });
241 }
242 frameSize.UpdateIllegalSizeWithCheck(SizeF { 0.0f, 0.0f });
243 layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
244 }
245
GetFirstValidRadius(const BorderRadiusProperty & borderRadius)246 Dimension ButtonLayoutAlgorithm::GetFirstValidRadius(const BorderRadiusProperty& borderRadius)
247 {
248 if (borderRadius.radiusTopLeft.has_value()) {
249 return borderRadius.radiusTopLeft.value();
250 }
251 if (borderRadius.radiusTopRight.has_value()) {
252 return borderRadius.radiusTopRight.value();
253 }
254 if (borderRadius.radiusBottomLeft.has_value()) {
255 return borderRadius.radiusBottomLeft.value();
256 }
257 if (borderRadius.radiusBottomRight.has_value()) {
258 return borderRadius.radiusBottomRight.value();
259 }
260 return 0.0_vp;
261 }
262 } // namespace OHOS::Ace::NG
263