1 /*
2  * Copyright (c) 2021-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/picker/render_picker_option.h"
17 
18 #include "base/log/event_report.h"
19 #include "core/components/picker/picker_option_component.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 const Dimension PRESS_INTERVAL = 4.0_vp;
25 const Dimension PRESS_RADIUS = 8.0_vp;
26 const Dimension FOCUS_AUTO_DIFF = 2.0_vp;
27 const Dimension FOCUS_RADIUS_AUTO_DIFF = 3.0_vp;
28 const Color PRESS_COLOR(0x19000000);
29 const Color HOVER_COLOR(0x0C000000);
30 
31 } // namespace
32 
RenderPickerOption()33 RenderPickerOption::RenderPickerOption()
34 {
35     if (SystemProperties::GetDeviceType() == DeviceType::WATCH ||
36         SystemProperties::GetDeviceType() == DeviceType::UNKNOWN) {
37         return;
38     }
39 
40     pressDecoration_ = AceType::MakeRefPtr<Decoration>();
41     pressDecoration_->SetBackgroundColor(PRESS_COLOR);
42     pressDecoration_->SetBorderRadius(Radius(PRESS_RADIUS));
43 
44     hoverDecoration_ = AceType::MakeRefPtr<Decoration>();
45     hoverDecoration_->SetBackgroundColor(HOVER_COLOR);
46     hoverDecoration_->SetBorderRadius(Radius(PRESS_RADIUS));
47 }
48 
Update(const RefPtr<Component> & component)49 void RenderPickerOption::Update(const RefPtr<Component>& component)
50 {
51     auto option = AceType::DynamicCast<PickerOptionComponent>(component);
52     if (!option) {
53         LOGE("input component is incorrect type or null.");
54         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
55         return;
56     }
57 
58     auto theme = option->GetTheme();
59     if (!theme) {
60         LOGE("option theme is null.");
61         EventReport::SendComponentException(ComponentExcepType::GET_THEME_ERR);
62         return;
63     }
64 
65     if (pressDecoration_) {
66         pressDecoration_->SetBackgroundColor(theme->GetPressColor());
67     }
68     if (hoverDecoration_) {
69         hoverDecoration_->SetBackgroundColor(HOVER_COLOR);
70     }
71     optionSize_ = theme->GetOptionSize(option->GetSelected());
72     if (option->GetDefaultHeight()) {
73         optionDefaultHeight_ = option->GetDefaultHeight();
74         if (NormalizeToPx(option->GetFixHeight()) > 0) {
75             if (option->GetFixHeight().Unit() == DimensionUnit::PERCENT) {
76                 optionSize_.SetHeight(optionSize_.Height() * option->GetFixHeight().Value());
77             } else {
78                 optionSize_.SetHeight(NormalizeToPx(option->GetFixHeight()));
79             }
80         } else {
81             optionSize_.SetHeight(0);
82         }
83     }
84     optionSizeUnit_ = theme->GetOptionSizeUnit();
85     optionPadding_ = theme->GetOptionPadding();
86     textComponent_ = option->GetTextComponent();
87     boxComponent_ = option->GetBoxComponent();
88     selectedStyle_ = theme->GetOptionStyle(true, false);
89     focusStyle_ = theme->GetOptionStyle(true, false);
90     focusColor_ = theme->GetFocusColor();
91     rrectRadius_ = theme->GetFocusRadius();
92     selectedDecoration_ = theme->GetOptionDecoration(false);
93     focusDecoration_ = theme->GetOptionDecoration(true);
94     index_ = option->GetIndex();
95     text_ = option->GetText();
96     selected_ = option->GetSelected();
97     autoLayout_ = option->GetAutoLayout();
98     alignTop_ = option->GetAlignTop();
99     alignBottom_ = option->GetAlignBottom();
100     MarkNeedLayout();
101 }
102 
GetSelected() const103 bool RenderPickerOption::GetSelected() const
104 {
105     return selected_;
106 }
107 
UpdateValue(uint32_t newIndex,const std::string & newText)108 void RenderPickerOption::UpdateValue(uint32_t newIndex, const std::string& newText)
109 {
110     index_ = newIndex;
111     text_ = newText;
112     if (!textComponent_) {
113         LOGE("text component is null in picker option.");
114         return;
115     }
116 
117     if (textComponent_->GetData() == text_) {
118         LOGE("The text does not change and does not need to be updated.");
119         return; // needless to update
120     }
121 
122     textComponent_->SetData(text_);
123     if (!renderText_) {
124         LOGE("render text is null in picker option.");
125         return;
126     }
127     renderText_->Update(textComponent_);
128 }
129 
OnTouchTestHit(const Offset &,const TouchRestrict &,TouchTestResult & result)130 void RenderPickerOption::OnTouchTestHit(const Offset&, const TouchRestrict&, TouchTestResult& result)
131 {
132     if (!selected_ && !autoLayout_) {
133         return;
134     }
135 
136     if (!pressDetect_) {
137         pressDetect_ = AceType::MakeRefPtr<PressRecognizer>(context_);
138         pressDetect_->SetOnPress([weak = AceType::WeakClaim(this)] (const PressInfo&) {
139             auto refPtr = weak.Upgrade();
140             if (!refPtr) {
141                 return;
142             }
143             refPtr->StartPressAnimation(true);
144         });
145         pressDetect_->SetOnPressCancel([weak = AceType::WeakClaim(this)] {
146             auto refPtr = weak.Upgrade();
147             if (!refPtr) {
148                 return;
149             }
150             refPtr->StartPressAnimation(false);
151         });
152     }
153     result.emplace_back(pressDetect_);
154 }
155 
OnMouseHoverEnterTest()156 void RenderPickerOption::OnMouseHoverEnterTest()
157 {
158     if (!selected_ || disabled_) {
159         return;
160     }
161     StartHoverAnimation(true);
162 }
163 
OnMouseHoverExitTest()164 void RenderPickerOption::OnMouseHoverExitTest()
165 {
166     if (!selected_ || disabled_) {
167         return;
168     }
169     StartHoverAnimation(false);
170 }
171 
UpdateBackgroundDecoration(const Color & color)172 void RenderPickerOption::UpdateBackgroundDecoration(const Color& color)
173 {
174     if (!pressDecoration_) {
175         return;
176     }
177     pressDecoration_->SetBackgroundColor(GetEventEffectColor());
178     boxComponent_->SetBackDecoration(pressDecoration_);
179     renderBox_->Update(boxComponent_);
180     MarkNeedRender();
181 }
182 
ResetMouseController()183 void RenderPickerOption::ResetMouseController()
184 {
185     if (!mouseAnimationController_) {
186         mouseAnimationController_ = CREATE_ANIMATOR(context_);
187     }
188     if (mouseAnimationController_->IsRunning()) {
189         mouseAnimationController_->Stop();
190     }
191     mouseAnimationController_->ClearInterpolators();
192     mouseAnimationController_->ClearAllListeners();
193 }
194 
ResetHoverAnimation(bool isEnter)195 bool RenderPickerOption::ResetHoverAnimation(bool isEnter)
196 {
197     RefPtr<PickerTheme> theme = GetTheme<PickerTheme>();
198     if (!theme) {
199         LOGE("picker option theme invalid");
200         return false;
201     }
202     if (!mouseAnimationController_) {
203         ResetMouseController();
204     }
205 
206     Color bgColor = GetEventEffectColor();
207     if (selectedDecoration_) {
208         bgColor = selectedDecoration_->GetBackgroundColor();
209     }
210     RefPtr<KeyframeAnimation<Color>> animation = AceType::MakeRefPtr<KeyframeAnimation<Color>>();
211     if (isEnter) {
212         // hover enter
213         CreateMouseAnimation(animation, bgColor, bgColor.BlendColor(HOVER_COLOR));
214         animation->SetCurve(Curves::FRICTION);
215     } else {
216         // from hover to normal
217         CreateMouseAnimation(animation, GetEventEffectColor(), bgColor);
218         if (GetEventEffectColor() == bgColor.BlendColor(HOVER_COLOR)) {
219             animation->SetCurve(Curves::FRICTION);
220         } else {
221             animation->SetCurve(Curves::FAST_OUT_SLOW_IN);
222         }
223     }
224     mouseAnimationController_->SetDuration(HOVER_DURATION);
225     mouseAnimationController_->AddInterpolator(animation);
226     mouseAnimationController_->SetFillMode(FillMode::FORWARDS);
227     return true;
228 }
229 
ResetPressAnimation(bool isDown)230 bool RenderPickerOption::ResetPressAnimation(bool isDown)
231 {
232     RefPtr<PickerTheme> theme = GetTheme<PickerTheme>();
233     if (!theme) {
234         LOGE("picker option theme invalid");
235         return false;
236     }
237     if (!mouseAnimationController_) {
238         ResetMouseController();
239     }
240 
241     auto pressColor = theme->GetPressColor();
242     Color bgColor = GetEventEffectColor();
243     if (selectedDecoration_) {
244         bgColor = selectedDecoration_->GetBackgroundColor();
245     }
246     RefPtr<KeyframeAnimation<Color>> animation = AceType::MakeRefPtr<KeyframeAnimation<Color>>();
247 
248     if (isDown) {
249         if (mouseState_ == MouseState::HOVER) {
250             // from hover to press
251             CreateMouseAnimation(animation, GetEventEffectColor(), bgColor.BlendColor(pressColor));
252         } else {
253             // from normal to press
254             CreateMouseAnimation(animation, bgColor, bgColor.BlendColor(pressColor));
255         }
256     } else {
257         if (mouseState_ == MouseState::HOVER) {
258             // from press to hover
259             CreateMouseAnimation(animation, GetEventEffectColor(), bgColor.BlendColor(HOVER_COLOR));
260         } else {
261             // from press to normal
262             CreateMouseAnimation(animation, GetEventEffectColor(), bgColor);
263         }
264     }
265     mouseAnimationController_->SetDuration(PRESS_DURATION);
266     mouseAnimationController_->AddInterpolator(animation);
267     mouseAnimationController_->SetFillMode(FillMode::FORWARDS);
268     return true;
269 }
270 
StartHoverAnimation(bool isEnter)271 void RenderPickerOption::StartHoverAnimation(bool isEnter)
272 {
273     ResetMouseController();
274     SetHoverAndPressCallback([weakNode = AceType::WeakClaim(this)](const Color& color) {
275         auto node = weakNode.Upgrade();
276         if (node) {
277             node->UpdateBackgroundDecoration(color);
278         }
279     });
280     if (mouseAnimationController_ && ResetHoverAnimation(isEnter)) {
281         mouseAnimationController_->Forward();
282     }
283 }
284 
StartPressAnimation(bool isDown)285 void RenderPickerOption::StartPressAnimation(bool isDown)
286 {
287     ResetMouseController();
288     SetHoverAndPressCallback([weakNode = AceType::WeakClaim(this)](const Color& color) {
289         auto node = weakNode.Upgrade();
290         if (node) {
291             node->UpdateBackgroundDecoration(color);
292         }
293     });
294     if (mouseAnimationController_ && ResetPressAnimation(isDown)) {
295         mouseAnimationController_->Forward();
296     }
297 }
298 
UpdateTextFocus(bool focus)299 void RenderPickerOption::UpdateTextFocus(bool focus)
300 {
301     hasTextFocus_ = focus;
302 
303     if (renderText_ && textComponent_) {
304         if (focus) {
305             textComponent_->SetTextStyle(focusStyle_);
306         } else {
307             textComponent_->SetTextStyle(selectedStyle_);
308         }
309         renderText_->Update(textComponent_);
310     }
311 }
312 
UpdatePhoneFocus(bool focus)313 void RenderPickerOption::UpdatePhoneFocus(bool focus)
314 {
315     if (SystemProperties::GetDeviceType() != DeviceType::PHONE) {
316         return;
317     }
318 
319     auto pipeline = context_.Upgrade();
320     if (!pipeline) {
321         LOGE("pipeline is null.");
322         return;
323     }
324 
325     if (focus) {
326         hasAnimate_ = true;
327         auto size = realSize_;
328         auto offset = GetGlobalOffset();
329         double radiusValue = NormalizeToPx(PRESS_RADIUS) - NormalizeToPx(FOCUS_RADIUS_AUTO_DIFF);
330         Radius pxRadius(radiusValue, radiusValue);
331         double yOffsetDiff = NormalizeToPx(PRESS_INTERVAL) + NormalizeToPx(FOCUS_AUTO_DIFF);
332         double xOffsetDiff = NormalizeToPx(FOCUS_AUTO_DIFF);
333         double xSizeDiff = 2.0 * xOffsetDiff;
334         double ySizeDiff = 2.0 * yOffsetDiff;
335         size = size - Size(xSizeDiff, ySizeDiff);
336         offset = offset + Size(xOffsetDiff, yOffsetDiff);
337         pipeline->ShowFocusAnimation(
338             RRect::MakeRRect(Rect(Offset(0, 0), size), pxRadius), focusColor_, offset);
339     } else {
340         hasAnimate_ = false;
341     }
342 }
343 
UpdateFocus(bool focus)344 void RenderPickerOption::UpdateFocus(bool focus)
345 {
346     if (SystemProperties::GetDeviceType() != DeviceType::TV) {
347         UpdateTextFocus(focus);
348         UpdatePhoneFocus(focus);
349         return;
350     }
351 
352     if (renderText_ && renderBox_ && textComponent_ && boxComponent_ && focusDecoration_ && selectedDecoration_) {
353         if (focus) {
354             textComponent_->SetTextStyle(focusStyle_);
355             boxComponent_->SetBackDecoration(focusDecoration_);
356         } else {
357             textComponent_->SetTextStyle(selectedStyle_);
358             boxComponent_->SetBackDecoration(selectedDecoration_);
359         }
360         renderText_->Update(textComponent_);
361         renderBox_->Update(boxComponent_);
362     } else {
363         LOGE("inner params has null.");
364     }
365 
366     auto pipeline = context_.Upgrade();
367     if (!pipeline) {
368         LOGE("pipeline is null.");
369         return;
370     }
371 
372     if (focus) {
373         hasAnimate_ = true;
374         Radius pxRadius(NormalizeToPx(rrectRadius_.GetX()), NormalizeToPx(rrectRadius_.GetY()));
375         pipeline->ShowFocusAnimation(
376             RRect::MakeRRect(Rect(Offset(0, 0), realSize_), pxRadius), focusColor_, GetGlobalOffset());
377     } else {
378         hasAnimate_ = false;
379     }
380 }
381 
RefreshFocus()382 void RenderPickerOption::RefreshFocus()
383 {
384     if (SystemProperties::GetDeviceType() != DeviceType::TV) {
385         UpdatePhoneFocus(hasAnimate_);
386         return;
387     }
388 
389     auto pipeline = context_.Upgrade();
390     if (!pipeline) {
391         LOGE("pipeline is null.");
392         return;
393     }
394 
395     if (hasAnimate_) {
396         Radius pxRadius(NormalizeToPx(rrectRadius_.GetX()), NormalizeToPx(rrectRadius_.GetY()));
397         pipeline->ShowFocusAnimation(
398             RRect::MakeRRect(Rect(Offset(0, 0), realSize_), pxRadius), focusColor_, GetGlobalOffset());
399     }
400 }
401 
UpdateScrollDelta(double delta)402 void RenderPickerOption::UpdateScrollDelta(double delta)
403 {
404     deltaSize_ = delta;
405     MarkNeedLayout();
406 }
407 
LayoutBox()408 double RenderPickerOption::LayoutBox()
409 {
410     LayoutParam boxLayout;
411     if (SystemProperties::GetDeviceType() != DeviceType::WATCH &&
412         SystemProperties::GetDeviceType() != DeviceType::UNKNOWN && selected_ && !autoLayout_) {
413         auto pressInterval = NormalizeToPx(PRESS_INTERVAL);
414         auto boxSize = realSize_;
415         boxSize.SetHeight(boxSize.Height() - 2.0 * pressInterval); // 2.0: subtract two pressInterval
416         boxLayout.SetFixedSize(boxSize);
417         renderBox_->SetPosition(Offset(0.0, pressInterval));
418         renderBox_->Layout(boxLayout);
419         return pressInterval;
420     } else {
421         boxLayout.SetFixedSize(realSize_);
422         renderBox_->SetPosition(Offset(0.0, 0.0));
423         renderBox_->Layout(boxLayout);
424         return 0.0;
425     }
426 }
427 
PerformLayout()428 void RenderPickerOption::PerformLayout()
429 {
430     if (!renderBox_ || !renderText_) {
431         LOGE("render text or render box is null.");
432         return;
433     }
434 
435     renderText_->Layout(GetLayoutParam());
436     Size textSize = renderText_->GetLayoutSize();
437     realPadding_ = NormalizeToPx(Dimension(optionPadding_, optionSizeUnit_));
438 
439     if (autoLayout_) {
440         realSize_ = renderText_->GetLayoutSize();
441     } else {
442         realSize_.SetWidth(NormalizeToPx(Dimension(optionSize_.Width(), optionSizeUnit_)));
443         realSize_.SetHeight(NormalizeToPx(Dimension(optionSize_.Height(), optionSizeUnit_)));
444     }
445 
446     if (realSize_.Width() - textSize.Width() < realPadding_) {
447         realSize_.SetWidth(textSize.Width() + realPadding_);
448     }
449     if (realSize_.Height() - textSize.Height() < realPadding_ && !optionDefaultHeight_) {
450         realSize_.SetHeight(textSize.Height() + realPadding_);
451     }
452 
453     double maxWidth = GetLayoutParam().GetMaxSize().Width();
454     if (realSize_.Width() > maxWidth) {
455         realSize_.SetWidth(maxWidth);
456     }
457     if (textSize.Width() > maxWidth - realPadding_) {
458         textSize.SetWidth(maxWidth - realPadding_);
459     }
460 
461     auto pressInterval = LayoutBox();
462 
463     LayoutParam textLayout;
464     textLayout.SetFixedSize(textSize);
465     double textX = (realSize_.Width() - textSize.Width()) / 2.0; // place center
466     if (textComponent_ && textComponent_->GetTextDirection() == TextDirection::RTL) {
467         textX = realSize_.Width() - realPadding_ / 2.0 - textSize.Width(); // place right; right padding is half
468     }
469     double textY = (realSize_.Height() - textSize.Height()) / 2.0; // place center
470     if (alignTop_) {
471         textY = 0.0;                                               // place top
472     } else if (alignBottom_) {
473         textY = realSize_.Height() - textSize.Height();            // place bottom
474     }
475     textY += deltaSize_;                                           // think about delta of scroll action.
476     textY -= pressInterval;
477     renderText_->SetPosition(Offset(textX, textY));
478     renderText_->Layout(textLayout);
479 
480     SetLayoutSize(realSize_);
481 }
482 
OnPaintFinish()483 void RenderPickerOption::OnPaintFinish()
484 {
485     if (!autoLayout_ && !selected_) {
486         return;
487     }
488 
489     RefreshFocus();
490 }
491 
UpdateRenders()492 void RenderPickerOption::UpdateRenders()
493 {
494     ClearRenders();
495     GetRenders();
496 }
497 
GetRenders(const RefPtr<RenderNode> & render)498 void RenderPickerOption::GetRenders(const RefPtr<RenderNode>& render)
499 {
500     if (!render) {
501         LOGE("render node is null.");
502         return;
503     }
504 
505     if (AceType::InstanceOf<RenderText>(render)) {
506         renderText_ = AceType::DynamicCast<RenderText>(render);
507         return;
508     }
509 
510     if (AceType::InstanceOf<RenderBox>(render)) {
511         renderBox_ = AceType::DynamicCast<RenderBox>(render);
512     }
513 
514     for (const auto& child : render->GetChildren()) {
515         GetRenders(child);
516     }
517 }
518 
GetRenders()519 void RenderPickerOption::GetRenders()
520 {
521     GetRenders(AceType::Claim(this));
522 }
523 
ClearRenders()524 void RenderPickerOption::ClearRenders()
525 {
526     renderText_ = nullptr;
527     renderBox_ = nullptr;
528 }
529 
HandleMouseHoverEvent(MouseState mouseState)530 void RenderPickerOption::HandleMouseHoverEvent(MouseState mouseState)
531 {
532     if (mouseState == MouseState::HOVER) {
533         OnMouseHoverEnterTest();
534     } else {
535         OnMouseHoverExitTest();
536     }
537 }
538 
539 } // namespace OHOS::Ace
540