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