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