1 /*
2  * Copyright (c) 2022-2023 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/text_picker/textpicker_layout_algorithm.h"
17 #include <cstdint>
18 
19 #include "core/components/dialog/dialog_theme.h"
20 #include "core/components/picker/picker_theme.h"
21 #include "core/components_ng/pattern/text_picker/textpicker_layout_property.h"
22 #include "core/components_ng/pattern/text_picker/textpicker_pattern.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 
28 namespace {
29 const int32_t DIVIDER_SIZE = 2;
30 const float PICKER_HEIGHT_HALF = 3.5f;
31 const float ITEM_HEIGHT_HALF = 2.0f;
32 const int32_t MAX_HALF_DISPLAY_COUNT = 2;
33 const int32_t BUFFER_NODE_NUMBER = 2;
34 const float DOUBLE_VALUE = 2.0f;
35 constexpr double PERCENT_100 = 100.0;
36 constexpr double PERCENT_120 = 1.2f;
37 
CreatePercentGradientColor(float percent,Color color)38 GradientColor CreatePercentGradientColor(float percent, Color color)
39 {
40     NG::GradientColor gredient = GradientColor(color);
41     gredient.SetDimension(CalcDimension(percent * PERCENT_100, DimensionUnit::PERCENT));
42     return gredient;
43 }
44 } // namespace
Measure(LayoutWrapper * layoutWrapper)45 void TextPickerLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
46 {
47     auto pipeline = PipelineContext::GetCurrentContext();
48     CHECK_NULL_VOID(pipeline);
49     auto pickerTheme = pipeline->GetTheme<PickerTheme>();
50     CHECK_NULL_VOID(pickerTheme);
51     auto dialogTheme = pipeline->GetTheme<DialogTheme>();
52     CHECK_NULL_VOID(dialogTheme);
53     SizeF frameSize = { -1.0f, -1.0f };
54 
55     auto columnNode = layoutWrapper->GetHostNode();
56     CHECK_NULL_VOID(columnNode);
57     auto blendNode = DynamicCast<FrameNode>(columnNode->GetParent());
58     CHECK_NULL_VOID(blendNode);
59     auto stackNode = DynamicCast<FrameNode>(blendNode->GetParent());
60     CHECK_NULL_VOID(stackNode);
61     auto pickerNode = DynamicCast<FrameNode>(stackNode->GetParent());
62     CHECK_NULL_VOID(pickerNode);
63     auto layoutProperty = pickerNode->GetLayoutProperty<TextPickerLayoutProperty>();
64     CHECK_NULL_VOID(layoutProperty);
65     auto textPickerPattern = pickerNode->GetPattern<TextPickerPattern>();
66     CHECK_NULL_VOID(textPickerPattern);
67 
68     GetColumnSize(layoutProperty, pickerTheme, dialogTheme, frameSize, pickerNode);
69 
70     textPickerPattern->CheckAndUpdateColumnSize(frameSize, NeedAdaptForAging());
71     pickerItemHeight_ = frameSize.Height();
72     layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
73     auto layoutChildConstraint = blendNode->GetLayoutProperty()->CreateChildConstraint();
74     for (auto&& child : layoutWrapper->GetAllChildrenWithBuild()) {
75         child->Measure(layoutChildConstraint);
76     }
77     MeasureText(layoutWrapper, frameSize);
78     float gradientPercent = GetGradientPercent(layoutProperty, textPickerPattern, frameSize, pickerTheme);
79     InitGradient(gradientPercent, blendNode, columnNode);
80 }
81 
GetGradientPercent(const RefPtr<TextPickerLayoutProperty> & layoutProperty,const RefPtr<TextPickerPattern> & textPickerPattern,SizeF & frameSize,const RefPtr<PickerTheme> & pickerTheme)82 float TextPickerLayoutAlgorithm::GetGradientPercent(const RefPtr<TextPickerLayoutProperty>& layoutProperty,
83     const RefPtr<TextPickerPattern>& textPickerPattern, SizeF& frameSize, const RefPtr<PickerTheme>& pickerTheme)
84 {
85     float gradientPercent = 0.0f;
86     bool isGradientHeight = layoutProperty->HasGradientHeight();
87     if (LessNotEqual(textPickerPattern->GetGradientHeight().ConvertToPx(), 0.0)) {
88         isGradientHeight = false;
89     }
90     if (isGradientHeight) {
91         auto gradientheight = textPickerPattern->GetGradientHeight();
92         float gradientheightValue = 0.0f;
93         if (gradientheight.Unit() == DimensionUnit::PERCENT) {
94             gradientheightValue = frameSize.Height() * gradientheight.Value() / DOUBLE_VALUE;
95         } else {
96             gradientheightValue = gradientheight.ConvertToPx();
97         }
98         if ((frameSize.Height() / DOUBLE_VALUE) < gradientheightValue) {
99             gradientPercent = static_cast<float>
100                 (pickerTheme->GetGradientHeight().ConvertToPx()) * gradientFontScale_ / frameSize.Height();
101         } else {
102             gradientPercent = gradientheightValue / frameSize.Height();
103         }
104     } else {
105         gradientPercent = static_cast<float>(pickerTheme->GetGradientHeight().ConvertToPx()) * gradientFontScale_ /
106                           frameSize.Height();
107     }
108     return gradientPercent;
109 }
110 
GetColumnSize(const RefPtr<TextPickerLayoutProperty> & layoutProperty,const RefPtr<PickerTheme> & pickerTheme,const RefPtr<DialogTheme> & dialogTheme,SizeF & frameSize,const RefPtr<FrameNode> & pickerNode)111 void TextPickerLayoutAlgorithm::GetColumnSize(const RefPtr<TextPickerLayoutProperty>& layoutProperty,
112     const RefPtr<PickerTheme>& pickerTheme, const RefPtr<DialogTheme>& dialogTheme, SizeF& frameSize,
113     const RefPtr<FrameNode>& pickerNode)
114 {
115     float pickerHeight = 0.0f;
116     isDefaultPickerItemHeight_ = layoutProperty->HasDefaultPickerItemHeight();
117     if (isDefaultPickerItemHeight_) {
118         auto defaultPickerItemHeightValue = layoutProperty->GetDefaultPickerItemHeightValue();
119         if (LessOrEqual(defaultPickerItemHeightValue.Value(), 0.0)) {
120             isDefaultPickerItemHeight_ = false;
121         } else {
122             UpdateDefaultPickerItemHeightLPX(pickerNode, defaultPickerItemHeightValue);
123         }
124     }
125 
126     uint32_t showCount_ = pickerTheme->GetShowCountPortrait();
127     if (SystemProperties::GetDeviceOrientation() == DeviceOrientation::LANDSCAPE) {
128         showCount_ = pickerTheme->GetShowCountLandscape();
129     }
130     auto textPickerPattern = pickerNode->GetPattern<TextPickerPattern>();
131     CHECK_NULL_VOID(textPickerPattern);
132     auto isUserSetDividerSpacingFont = textPickerPattern->GetIsUserSetDividerSpacingFont();
133     auto isUserSetGradientFont = textPickerPattern->GetIsUserSetGradientFont();
134     if (isUserSetDividerSpacingFont) {
135         dividerSpacingFontScale_ = ReCalcItemHeightScale(textPickerPattern->GetDividerSpacing());
136         textPickerPattern->SetPaintDividerSpacing(dividerSpacingFontScale_);
137     }
138 
139     if (isUserSetGradientFont) {
140         gradientFontScale_ = ReCalcItemHeightScale(textPickerPattern->GetGradientHeight(), false);
141     }
142 
143     if (isDefaultPickerItemHeight_) {
144         pickerHeight = static_cast<float>(defaultPickerItemHeight_ * showCount_);
145     } else {
146         pickerHeight = static_cast<float>(pickerTheme->GetGradientHeight().ConvertToPx() *
147                                               (static_cast<int32_t>(showCount_) - 1) * gradientFontScale_ +
148                                           pickerTheme->GetDividerSpacing().ConvertToPx() * dividerSpacingFontScale_);
149     }
150 
151     auto layoutConstraint = pickerNode->GetLayoutProperty()->GetLayoutConstraint();
152     float pickerWidth = static_cast<float>((pickerTheme->GetDividerSpacing() * DIVIDER_SIZE).ConvertToPx());
153 
154     if (textPickerPattern->GetIsShowInDialog() && isDefaultPickerItemHeight_) {
155         float dialogButtonHeight =
156             static_cast<float>((pickerTheme->GetButtonHeight() + dialogTheme->GetDividerHeight() +
157                                 dialogTheme->GetDividerPadding().Bottom() + pickerTheme->GetContentMarginVertical() * 2)
158                                 .ConvertToPx());
159         pickerHeight = std::min(pickerHeight, layoutConstraint->maxSize.Height() - dialogButtonHeight);
160         if (!NearZero(showCount_)) {
161             defaultPickerItemHeight_ = pickerHeight / showCount_;
162         }
163         textPickerPattern->SetResizePickerItemHeight(defaultPickerItemHeight_);
164         textPickerPattern->SetResizeFlag(true);
165     }
166 
167     frameSize.SetWidth(pickerWidth);
168     frameSize.SetHeight(pickerHeight);
169 }
170 
UpdateDefaultPickerItemHeightLPX(const RefPtr<FrameNode> & pickerNode,const Dimension & defaultPickerItemHeightValue)171 void TextPickerLayoutAlgorithm::UpdateDefaultPickerItemHeightLPX(
172     const RefPtr<FrameNode>& pickerNode, const Dimension& defaultPickerItemHeightValue)
173 {
174     if (defaultPickerItemHeight_ != defaultPickerItemHeightValue.Value() &&
175         defaultPickerItemHeightValue.Unit() == DimensionUnit::LPX) {
176         CHECK_NULL_VOID(pickerNode);
177         auto context = pickerNode->GetContext();
178         CHECK_NULL_VOID(context);
179         defaultPickerItemHeight_ = context->NormalizeToPx(defaultPickerItemHeightValue);
180     }
181 }
182 
InitGradient(const float & gradientPercent,const RefPtr<FrameNode> blendNode,const RefPtr<FrameNode> columnNode)183 void TextPickerLayoutAlgorithm::InitGradient(const float& gradientPercent, const RefPtr<FrameNode> blendNode,
184     const RefPtr<FrameNode> columnNode)
185 {
186     auto blendRenderContext = blendNode->GetRenderContext();
187     auto columnRenderContext = columnNode->GetRenderContext();
188     CHECK_NULL_VOID(blendRenderContext);
189     CHECK_NULL_VOID(columnRenderContext);
190     NG::Gradient gradient;
191     gradient.CreateGradientWithType(NG::GradientType::LINEAR);
192     gradient.AddColor(CreatePercentGradientColor(0, Color::TRANSPARENT));
193     gradient.AddColor(CreatePercentGradientColor(gradientPercent, Color::WHITE));
194     gradient.AddColor(CreatePercentGradientColor(1 - gradientPercent, Color::WHITE));
195     gradient.AddColor(CreatePercentGradientColor(1, Color::TRANSPARENT));
196 
197     columnRenderContext->UpdateBackBlendMode(BlendMode::SRC_IN);
198     columnRenderContext->UpdateBackBlendApplyType(BlendApplyType::OFFSCREEN);
199     blendRenderContext->UpdateLinearGradient(gradient);
200     blendRenderContext->UpdateBackBlendMode(BlendMode::SRC_OVER);
201     blendRenderContext->UpdateBackBlendApplyType(BlendApplyType::OFFSCREEN);
202 }
203 
MeasureText(LayoutWrapper * layoutWrapper,const SizeF & size)204 void TextPickerLayoutAlgorithm::MeasureText(LayoutWrapper* layoutWrapper, const SizeF& size)
205 {
206     auto totalChild = layoutWrapper->GetTotalChildCount();
207     for (int32_t index = 0; index < totalChild; index++) {
208         auto child = layoutWrapper->GetOrCreateChildByIndex(index);
209         ChangeTextStyle(index, totalChild, size, child, layoutWrapper);
210     }
211 }
212 
ChangeTextStyle(uint32_t index,uint32_t showOptionCount,const SizeF & size,const RefPtr<LayoutWrapper> & childLayoutWrapper,LayoutWrapper * layoutWrapper)213 void TextPickerLayoutAlgorithm::ChangeTextStyle(uint32_t index, uint32_t showOptionCount, const SizeF& size,
214     const RefPtr<LayoutWrapper>& childLayoutWrapper, LayoutWrapper* layoutWrapper)
215 {
216     SizeF frameSize = { -1.0f, -1.0f };
217     auto pipeline = PipelineContext::GetCurrentContext();
218     CHECK_NULL_VOID(pipeline);
219     auto pickerTheme = pipeline->GetTheme<PickerTheme>();
220     CHECK_NULL_VOID(pickerTheme);
221     frameSize.SetWidth(size.Width());
222     uint32_t selectedIndex = showOptionCount / 2; // the center option is selected.
223     auto layoutChildConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
224     if (isDefaultPickerItemHeight_) {
225         frameSize.SetHeight(static_cast<float>(defaultPickerItemHeight_));
226     } else {
227         if (index == selectedIndex) {
228             frameSize.SetHeight(
229                 static_cast<float>(pickerTheme->GetDividerSpacing().ConvertToPx() * dividerSpacingFontScale_));
230         } else {
231             frameSize.SetHeight(
232                 static_cast<float>(pickerTheme->GetGradientHeight().ConvertToPx() * gradientFontScale_));
233         }
234     }
235     layoutChildConstraint.selfIdealSize = { frameSize.Width(), frameSize.Height() };
236     childLayoutWrapper->Measure(layoutChildConstraint);
237 }
238 
Layout(LayoutWrapper * layoutWrapper)239 void TextPickerLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
240 {
241     CHECK_NULL_VOID(layoutWrapper);
242     auto pipeline = PipelineContext::GetCurrentContext();
243     CHECK_NULL_VOID(pipeline);
244     auto pickerTheme = pipeline->GetTheme<PickerTheme>();
245     CHECK_NULL_VOID(pickerTheme);
246     auto layoutProperty = AceType::DynamicCast<LinearLayoutProperty>(layoutWrapper->GetLayoutProperty());
247     CHECK_NULL_VOID(layoutProperty);
248     auto geometryNode = layoutWrapper->GetGeometryNode();
249     CHECK_NULL_VOID(geometryNode);
250     auto size = geometryNode->GetFrameSize();
251     auto padding = layoutProperty->CreatePaddingAndBorder();
252     MinusPaddingToSize(padding, size);
253     auto children = layoutWrapper->GetAllChildrenWithBuild();
254     float childStartCoordinate = 0.0f;
255 
256     if (isDefaultPickerItemHeight_) {
257         childStartCoordinate +=
258             static_cast<float>(size.Height() / ITEM_HEIGHT_HALF - defaultPickerItemHeight_ * PICKER_HEIGHT_HALF);
259         halfDisplayCounts_ =
260             std::clamp(static_cast<int32_t>(
261                            std::ceil((size.Height() / ITEM_HEIGHT_HALF - defaultPickerItemHeight_ / ITEM_HEIGHT_HALF) /
262                                      defaultPickerItemHeight_)),
263                 0, MAX_HALF_DISPLAY_COUNT);
264     } else {
265         childStartCoordinate += static_cast<float>(pickerItemHeight_ / ITEM_HEIGHT_HALF -
266             pickerTheme->GetGradientHeight().ConvertToPx() * gradientFontScale_* (ITEM_HEIGHT_HALF + 1) -
267             pickerTheme->GetDividerSpacing().ConvertToPx() * dividerSpacingFontScale_ / ITEM_HEIGHT_HALF);
268         halfDisplayCounts_ = std::clamp(
269             static_cast<int32_t>(std::ceil((pickerItemHeight_ / ITEM_HEIGHT_HALF -
270                                                pickerTheme->GetDividerSpacing().ConvertToPx() / ITEM_HEIGHT_HALF) /
271                                            pickerTheme->GetGradientHeight().ConvertToPx())),
272             0, MAX_HALF_DISPLAY_COUNT);
273     }
274     int32_t i = 0;
275     int32_t showCount = static_cast<int32_t>(pickerTheme->GetShowOptionCount()) + BUFFER_NODE_NUMBER;
276     for (const auto& child : children) {
277         if (i >= showCount || i >= static_cast<int32_t>(currentOffset_.size())) {
278             break;
279         }
280         auto childGeometryNode = child->GetGeometryNode();
281         auto childSize = childGeometryNode->GetMarginFrameSize();
282         auto childOffset =
283             OffsetF(0.0, childStartCoordinate + static_cast<float>(currentOffset_[i++]) + padding.Offset().GetY());
284         childGeometryNode->SetMarginFrameOffset(childOffset);
285         child->Layout();
286         childStartCoordinate += childSize.Height();
287     }
288 }
289 
NeedAdaptForAging()290 bool TextPickerLayoutAlgorithm::NeedAdaptForAging()
291 {
292     auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
293     CHECK_NULL_RETURN(pipeline, false);
294     auto pickerTheme = pipeline->GetTheme<PickerTheme>();
295     CHECK_NULL_RETURN(pickerTheme, false);
296 
297     if (GreatOrEqual(pipeline->GetFontScale(), pickerTheme->GetMaxOneFontScale())) {
298         return true;
299     }
300     return false;
301 }
302 
AdjustFontSizeScale(const Dimension & fontSizeValue,double fontScale)303 const Dimension TextPickerLayoutAlgorithm::AdjustFontSizeScale(const Dimension& fontSizeValue, double fontScale)
304 {
305     auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
306     CHECK_NULL_RETURN(pipeline, fontSizeValue);
307     auto pickerTheme = pipeline->GetTheme<PickerTheme>();
308     CHECK_NULL_RETURN(pickerTheme, fontSizeValue);
309 
310     double adjustedScale = std::clamp(fontScale, pickerTheme->GetNormalFontScale(),
311         pickerTheme->GetMaxTwoFontScale());
312     auto result = 0.0_vp;
313     if (!NearZero(fontScale)) {
314         result =  fontSizeValue / fontScale * adjustedScale;
315     }
316     return result;
317 }
318 
ReCalcItemHeightScale(const Dimension & userSetHeight,bool isDividerSpacing)319 float TextPickerLayoutAlgorithm::ReCalcItemHeightScale(const Dimension& userSetHeight, bool isDividerSpacing)
320 {
321     auto fontScale = 1.0f;
322 
323     if (NeedAdaptForAging()) {
324         auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
325         CHECK_NULL_RETURN(pipeline, fontScale);
326         auto pickerTheme = pipeline->GetTheme<PickerTheme>();
327         CHECK_NULL_RETURN(pickerTheme, fontScale);
328         auto systemFontScale = static_cast<double>(pipeline->GetFontScale());
329         auto themePadding = pickerTheme->GetPickerDialogFontPadding();
330         auto userSetHeightValue = AdjustFontSizeScale(userSetHeight, systemFontScale).ConvertToPx();
331         double adjustedScale = std::clamp(systemFontScale, pickerTheme->GetNormalFontScale(),
332             pickerTheme->GetMaxTwoFontScale());
333         if (!NearZero(adjustedScale)) {
334             userSetHeightValue = userSetHeightValue / adjustedScale * PERCENT_120 +
335                 (themePadding.ConvertToPx() * DIVIDER_SIZE);
336         } else {
337             return fontScale;
338         }
339 
340         auto themeHeightLimit = isDividerSpacing ? pickerTheme->GetDividerSpacingLimit() :
341             pickerTheme->GetGradientHeightLimit();
342         auto themeHeight = isDividerSpacing ? pickerTheme->GetDividerSpacing() :
343             pickerTheme->GetGradientHeight();
344         if (GreatOrEqualCustomPrecision(userSetHeightValue, themeHeightLimit.ConvertToPx())) {
345             userSetHeightValue = themeHeightLimit.ConvertToPx();
346         } else {
347             userSetHeightValue = std::max(userSetHeightValue, themeHeight.ConvertToPx());
348         }
349         fontScale = std::max(static_cast<float>(userSetHeightValue / themeHeight.ConvertToPx()), fontScale);
350     }
351     return fontScale;
352 }
353 
354 } // namespace OHOS::Ace::NG
355