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