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