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