• 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 <cstdlib>
17 #include <cmath>
18 #include "core/components/checkable/render_switch.h"
19 
20 #include "base/log/event_report.h"
21 #include "core/common/font_manager.h"
22 #include "core/components/common/properties/alignment.h"
23 #include "core/components/text/text_component.h"
24 
25 namespace OHOS::Ace {
26 namespace {
27 
28 #ifdef WEARABLE_PRODUCT
29 constexpr int32_t DEFAULT_SWITCH_ANIMATION_DURATION = 250;
30 #else
31 constexpr int32_t DEFAULT_SWITCH_ANIMATION_DURATION = 450;
32 #endif
33 constexpr Dimension DEFAULT_SWITCH_POINT_PADDING = 2.0_vp;
34 
35 } // namespace
36 
~RenderSwitch()37 RenderSwitch::~RenderSwitch()
38 {
39 #ifndef WEARABLE_PRODUCT
40     UnSubscribeMultiModal();
41 #endif
42     auto context = context_.Upgrade();
43     if (context) {
44         context->RemoveFontNode(WeakClaim(this));
45         auto fontManager = context->GetFontManager();
46         if (fontManager) {
47             fontManager->RemoveVariationNode(WeakClaim(this));
48         }
49     }
50 }
51 
Update(const RefPtr<Component> & component)52 void RenderSwitch::Update(const RefPtr<Component>& component)
53 {
54     RenderCheckable::Update(component);
55     auto switchComponent = AceType::DynamicCast<SwitchComponent>(component);
56     if (!switchComponent) {
57         LOGE("cast to switch component failed");
58         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
59         return;
60     }
61     component_ = switchComponent;
62     showText_ = switchComponent->GetShowText();
63     if (showText_) {
64         if (!renderTextOn_ || !renderTextOff_) {
65             InitRenderText();
66         }
67         UpdateRenderText(switchComponent);
68     }
69 
70     if (!controller_) {
71         controller_ = AceType::MakeRefPtr<Animator>(GetContext());
72         auto weak = AceType::WeakClaim(this);
73         controller_->AddStopListener(Animator::StatusCallback([weak]() {
74             auto switchComponent = weak.Upgrade();
75             if (switchComponent) {
76                 switchComponent->OnAnimationStop();
77             }
78         }));
79     }
80     backgroundSolid_ = switchComponent->IsBackgroundSolid();
81     pointPadding_ = DEFAULT_SWITCH_POINT_PADDING;
82 
83     if (switchComponent->GetUpdateType() == UpdateType::ALL) {
84         checked_ = switchComponent->GetValue();
85     }
86 
87     ApplyRestoreInfo();
88 
89     oldChecked_ = checked_;
90     needReverse_ = (switchComponent->GetTextDirection() == TextDirection::RTL);
91     auto theme = GetTheme<SwitchTheme>();
92     if (theme) {
93         borderWidth_ = theme->GetBorderWidth();
94     }
95     if (!isDraging && !controller_->IsRunning()) {
96         UpdateUIStatus();
97     }
98     HandleDrag();
99     UpdateAccessibilityAttr();
100 #ifndef WEARABLE_PRODUCT
101     if (!component_->GetMultimodalProperties().IsUnavailable()) {
102         PrepareMultiModalEvent();
103         SubscribeMultiModal();
104     }
105 #endif
106     SetAccessibilityClickImpl();
107 }
108 
UpdateAccessibilityAttr()109 void RenderSwitch::UpdateAccessibilityAttr()
110 {
111     std::string text;
112     if (component_ && showText_) {
113         if (checked_) {
114             text = textOn_;
115         } else {
116             text = textOff_;
117         }
118     }
119     auto accessibilityNode = GetAccessibilityNode().Upgrade();
120     if (!accessibilityNode) {
121         return;
122     }
123     accessibilityNode->SetText(text);
124     accessibilityNode->SetCheckedState(checked_);
125     if (accessibilityNode->GetClicked()) {
126         accessibilityNode->SetClicked(false);
127         auto context = context_.Upgrade();
128         if (context) {
129             AccessibilityEvent switchEvent;
130             switchEvent.nodeId = accessibilityNode->GetNodeId();
131             switchEvent.eventType = "click";
132             context->SendEventToAccessibility(switchEvent);
133         }
134     }
135 }
136 
InitRenderText()137 void RenderSwitch::InitRenderText()
138 {
139     LayoutParam innerLayout;
140     innerLayout.SetMaxSize(Size(Size::INFINITE_SIZE, Size::INFINITE_SIZE));
141 
142     textOnComponent_ = AceType::MakeRefPtr<TextComponent>(textOn_);
143     renderTextOn_ = AceType::DynamicCast<RenderText>(textOnComponent_->CreateRenderNode());
144     renderTextOn_->Attach(GetContext());
145     renderTextOn_->Update(textOnComponent_);
146     renderTextOn_->SetLayoutParam(innerLayout);
147 
148     textOffComponent_ = AceType::MakeRefPtr<TextComponent>(textOff_);
149     renderTextOff_ = AceType::DynamicCast<RenderText>(textOffComponent_->CreateRenderNode());
150     renderTextOff_->Attach(GetContext());
151     renderTextOff_->Update(textOffComponent_);
152     renderTextOff_->SetLayoutParam(innerLayout);
153 
154     auto context = context_.Upgrade();
155     if (context) {
156         auto fontManager = context->GetFontManager();
157         if (fontManager) {
158             fontManager->AddVariationNode(WeakClaim(this));
159         }
160     }
161 }
162 
HandleDrag()163 void RenderSwitch::HandleDrag()
164 {
165     if (!disabled_ && !dragRecognizer_) {
166         dragRecognizer_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
167         dragRecognizer_->SetOnDragStart([weak = AceType::WeakClaim(this)](const DragStartInfo& info) {
168             auto renderSwitch = weak.Upgrade();
169             if (renderSwitch) {
170                 renderSwitch->HandleDragStart(info.GetLocalLocation());
171             }
172         });
173         dragRecognizer_->SetOnDragUpdate([weak = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
174             auto renderSwitch = weak.Upgrade();
175             if (renderSwitch) {
176                 renderSwitch->HandleDragUpdate(info.GetLocalLocation());
177             }
178         });
179         dragRecognizer_->SetOnDragEnd([weak = AceType::WeakClaim(this)](const DragEndInfo& info) {
180             auto renderSwitch = weak.Upgrade();
181             if (renderSwitch) {
182                 renderSwitch->HandleDragEnd(info.GetLocalLocation());
183             }
184         });
185     } else if (disabled_ && dragRecognizer_) {
186         dragRecognizer_ = nullptr;
187     }
188 }
189 
PerformLayout()190 void RenderSwitch::PerformLayout()
191 {
192     if (showText_) {
193         width_ = NormalizeToPx(defaultWidth_);
194         height_ = NormalizeToPx(defaultHeight_);
195         textOnSize_ = CalculateTextSize(textOn_, renderTextOn_);
196         textOffSize_ = CalculateTextSize(textOff_, renderTextOff_);
197     } else {
198         RenderCheckable::InitSize();
199     }
200     RenderCheckable::CalculateSize();
201     double maxTextWidth = std::max(textOnSize_.Width(), textOffSize_.Width());
202     double pointHeight = std::max(textOnSize_.Height(), drawSize_.Height());
203     auto pointTextPadding = NormalizeToPx(pointTextPadding_);
204     rawPointSize_ = Size(std::max(maxTextWidth + pointTextPadding * 2, pointHeight), pointHeight);
205     bool pointUseTextHeight = (rawPointSize_.Height() > drawSize_.Height());
206     bool pointUseHeightAsWidth = (rawPointSize_.Width() == drawSize_.Height());
207     auto pointPadding = NormalizeToPx(pointPadding_);
208     if (showText_) {
209         if (pointUseTextHeight) {
210             pointHeight += pointPadding * 2.0;
211         }
212         switchSize_ =
213             Size(pointUseHeightAsWidth ? drawSize_.Width() : (rawPointSize_.Width() + pointPadding) * aspectRatio_,
214                 pointHeight);
215         pointRadius_ = pointHeight / 2.0 - pointPadding;
216     } else {
217         switchSize_ = drawSize_;
218         pointRadius_ = drawSize_.Height() / 2.0 - pointPadding;
219     }
220     rawPointSize_ -=
221         Size(pointUseHeightAsWidth ? pointPadding * 2.0 : 0.0, pointUseTextHeight ? 0.0 : pointPadding * 2.0);
222 
223     Size layoutSize;
224     if (switchSize_.Width() > width_) {
225         layoutSize = GetLayoutParam().Constrain(Size(switchSize_.Width() + NormalizeToPx(hotZoneHorizontalPadding_) * 2,
226             std::max(switchSize_.Height(), height_) + NormalizeToPx(hotZoneVerticalPadding_) * 2));
227     } else {
228         layoutSize = GetLayoutParam().Constrain(Size(width_, height_));
229     }
230     SetLayoutSize(layoutSize);
231     double widthOverflow = layoutSize.Width() - switchSize_.Width();
232     paintPosition_ = Alignment::GetAlignPosition(layoutSize, switchSize_, Alignment::CENTER) +
233                      Offset(widthOverflow > 0.0 ? 0.0 : widthOverflow - NormalizeToPx(hotZoneHorizontalPadding_), 0.0);
234     if (!isDraging && !controller_->IsRunning()) {
235         InitCurrentPointPosition();
236     }
237     UpdateAccessibilityPosition();
238 }
239 
HandleDragStart(const Offset & updatePoint)240 void RenderSwitch::HandleDragStart(const Offset& updatePoint)
241 {
242     dragStartPosition_ = updatePoint.GetX();
243     oldChecked_ = checked_;
244 }
245 
HandleDragUpdate(const OHOS::Ace::Offset & updatePoint)246 void RenderSwitch::HandleDragUpdate(const OHOS::Ace::Offset& updatePoint)
247 {
248     OnDrag(updatePoint);
249     MarkNeedRender();
250 }
251 
HandleDragEnd(const OHOS::Ace::Offset & updatePoint)252 void RenderSwitch::HandleDragEnd(const OHOS::Ace::Offset& updatePoint)
253 {
254     OnDrag(updatePoint);
255     dragStartPosition_ = 0.0;
256     isDraging = false;
257     bool isNeedCallback = (oldChecked_ != checked_);
258     if (isNeedCallback && changeEvent_) {
259         std::string res = checked_ ? "true" : "false";
260         changeEvent_(std::string("\"change\",{\"checked\":").append(res.append("},null")));
261     }
262     oldChecked_ = checked_;
263     InitCurrentPointPosition();
264     UpdateUIStatus();
265     if (isNeedCallback && onChange_) {
266         onChange_(checked_);
267     }
268     auto accessibilityNode = accessibilityNode_.Upgrade();
269     if (accessibilityNode) {
270         accessibilityNode->SetCheckedState(checked_);
271     }
272     MarkNeedRender();
273 }
274 
UpdateRenderText(const RefPtr<SwitchComponent> & switchComponent)275 void RenderSwitch::UpdateRenderText(const RefPtr<SwitchComponent>& switchComponent)
276 {
277     auto context = context_.Upgrade();
278     if (context) {
279         if (textStyle_.IsAllowScale() || textStyle_.GetFontSize().Unit() == DimensionUnit::FP) {
280             context->AddFontNode(AceType::WeakClaim(this));
281         }
282     }
283 
284     textOn_ = switchComponent->GetTextOn();
285     textOff_ = switchComponent->GetTextOff();
286     textColorOn_ = switchComponent->GetTextColorOn();
287     textColorOff_ = switchComponent->GetTextColorOff();
288     pointTextPadding_ = switchComponent->GetTextPadding();
289 
290     textStyle_ = switchComponent->GetTextStyle();
291     textStyle_.SetTextColor(textColorOn_);
292     textOnComponent_->SetData(textOn_);
293     textOnComponent_->SetTextStyle(textStyle_);
294     renderTextOn_->Update(textOnComponent_);
295 
296     textStyle_.SetTextColor(textColorOff_);
297     textOffComponent_->SetData(textOff_);
298     textOffComponent_->SetTextStyle(textStyle_);
299     renderTextOff_->Update(textOffComponent_);
300 }
301 
OnDrag(const Offset & updatePoint)302 void RenderSwitch::OnDrag(const Offset& updatePoint)
303 {
304     isDraging = true;
305     InitCurrentPointPosition();
306     auto pointPadding = NormalizeToPx(pointPadding_);
307     double pointTrackLength = switchSize_.Width() - rawPointSize_.Width() - pointPadding * 2;
308     double halfTrackLength = pointTrackLength / 2.0;
309     pointPositionDelta_ = updatePoint.GetX() - dragStartPosition_;
310     // let A = [needReverse_], B = [oldChecked_], C = [moveRight], D = [effectiveMove],
311     // and [A'] represents the reverse of [A].
312     // there are four situations in which the checked status needs to be reversed:
313     // 1. A'B'CD, 2. A'BC'D, 3. AB'C'D, 4. ABCD,
314     // so [needChangeStatus] = A'B'CD + ABCD + A'BC'D + AB'C'D = (A'B' + AB)CD + (A'B + AB')C'D
315     bool effectiveMove = std::abs(pointPositionDelta_) > halfTrackLength;
316     bool moveRight = pointPositionDelta_ > 0.0;
317     bool needChangeStatus = ((needReverse_ == oldChecked_) && moveRight && effectiveMove) ||
318                             ((needReverse_ != oldChecked_) && !moveRight && effectiveMove);
319     checked_ = needChangeStatus != oldChecked_;     // reverse [checked_] status if [needChangeStatus] is true
320 
321     // let A = [needReverse_], B = [oldChecked_], C = [moveRight], D = [excessiveMove],
322     // and [A'] represents the reverse of [A].
323     // there are eight situations in which [pointPositionDelta_] needs to be constrained to zero:
324     // 1. A'B'C'D', 2. A'B'C'D, 3. ABC'D', 4. ABC'D, 5. A'BCD', 6. A'BCD, 7. AB'CD', 8. AB'CD,
325     // so [constrainDeltaToZero] = (A'B'C'D' + A'B'C'D) +( ABC'D' + ABC'D) + (A'BCD' + A'BCD) + (AB'CD' + AB'CD)
326     //                           = A'B'C' + ABC' + A'BC + AB'C = (A'B' + AB)C' + (A'B + AB')C
327     bool excessiveMove = std::abs(pointPositionDelta_) > pointTrackLength;
328     bool constrainDeltaToZero =
329         (moveRight && (needReverse_ != oldChecked_)) || (!moveRight && (needReverse_ == oldChecked_));
330     // there are four situations in which the absolute value of [pointPositionDelta_] needs to be constrained to
331     // trackLength: 1. A'BC'D, 2. AB'C'D, 3. ABCD, 4. A'B'CD,
332     // so [constrainDeltaToTrackLength] = A'BC'D + AB'C'D + ABCD + A'B'CD = (A'BC' + AB'C' + ABC + A'B'C)D
333     //                                  = ((A'B + AB')C' + (AB + A'B')C)D
334     bool constrainDeltaToTrackLength = excessiveMove &&
335         ((!moveRight && (needReverse_ != oldChecked_)) || (moveRight && (needReverse_ == oldChecked_)));
336     double deltaCoefficient = moveRight ? 1.0 : -1.0;
337     if (constrainDeltaToZero) {
338         pointPositionDelta_ = 0.0;
339     } else if (constrainDeltaToTrackLength) {
340         pointPositionDelta_ = deltaCoefficient * pointTrackLength;
341     }
342     currentPointOriginX_ += pointPositionDelta_;
343 }
344 
HandleClick()345 void RenderSwitch::HandleClick()
346 {
347     if (controller_->IsRunning()) {
348         controller_->Stop();
349     }
350     UpdateAnimation();
351     controller_->Play();
352 
353     if (clickEvent_) {
354         clickEvent_();
355     }
356 }
357 
PaintText(const Offset & textOffset,RenderContext & context) const358 void RenderSwitch::PaintText(const Offset& textOffset, RenderContext& context) const
359 {
360     auto paintRenderText = oldChecked_ ? renderTextOn_ : renderTextOff_;
361     auto textSize = paintRenderText->GetLayoutSize();
362     textSize.SetHeight(textSize.Height() > rawPointSize_.Height() ? rawPointSize_.Height() : textSize.Height());
363     auto textPos = Alignment::GetAlignPosition(rawPointSize_, textSize, Alignment::CENTER);
364     paintRenderText->Paint(context, textOffset + textPos);
365 }
366 
OnAnimationStop()367 void RenderSwitch::OnAnimationStop()
368 {
369     // after the animation stopped,we need to update the check status
370     RenderCheckable::HandleClick();
371     UpdateAccessibilityAttr();
372     oldChecked_ = checked_;
373     InitCurrentPointPosition();
374 }
375 
UpdateAnimation()376 void RenderSwitch::UpdateAnimation()
377 {
378     double from = 0.0;
379     double to = 0.0;
380     auto pointPadding = NormalizeToPx(pointPadding_);
381     if ((oldChecked_ && !needReverse_) || (!oldChecked_ && needReverse_)) {
382         from = switchSize_.Width() - pointPadding - rawPointSize_.Width();
383         to = pointPadding;
384     } else {
385         from = pointPadding;
386         to = switchSize_.Width() - pointPadding - rawPointSize_.Width();
387     }
388 
389     if (translate_) {
390         controller_->RemoveInterpolator(translate_);
391     }
392     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::FRICTION);
393     auto weak = AceType::WeakClaim(this);
394     translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
395         auto switchComp = weak.Upgrade();
396         if (switchComp) {
397             Offset data(value, 0.0);
398             switchComp->UpdatePointPosition(data);
399         }
400     }));
401     controller_->SetDuration(DEFAULT_SWITCH_ANIMATION_DURATION);
402     controller_->AddInterpolator(translate_);
403 }
404 
UpdatePointPosition(const Offset & updatePoint)405 void RenderSwitch::UpdatePointPosition(const Offset& updatePoint)
406 {
407     InitCurrentPointPosition();
408     auto pointPadding = NormalizeToPx(pointPadding_);
409     double pointTrackLength = switchSize_.Width() - rawPointSize_.Width() - pointPadding * 2;
410     double movingOffset = updatePoint.GetX();
411     if (movingOffset < 0.0) {
412         movingOffset = 0.0;
413     } else if (movingOffset > pointTrackLength) {
414         movingOffset = pointTrackLength;
415     }
416     if ((oldChecked_ && !needReverse_) || (!oldChecked_ && needReverse_)) {
417         currentPointOriginX_ = movingOffset;
418         uiStatus_ = needReverse_ ? UIStatus::OFF_TO_ON : UIStatus::ON_TO_OFF;
419     } else {
420         uiStatus_ = needReverse_ ? UIStatus::ON_TO_OFF : UIStatus::OFF_TO_ON;
421         currentPointOriginX_ += movingOffset;
422     }
423     MarkNeedRender();
424 }
425 
426 #ifndef WEARABLE_PRODUCT
PrepareMultiModalEvent()427 void RenderSwitch::PrepareMultiModalEvent()
428 {
429     if (!multimodalSwitchEvent_) {
430         multimodalSwitchEvent_ = [weak = WeakClaim(this)](const AceMultimodalEvent& event) {
431             auto renderSwitch = weak.Upgrade();
432             static const int32_t switchOn = 1;
433             static const int32_t switchOff = 2;
434             if (renderSwitch) {
435                 if (event.GetVoice().action == switchOn) {
436                     renderSwitch->OpenSwitch();
437                 } else if (event.GetVoice().action == switchOff) {
438                     renderSwitch->CloseSwitch();
439                 }
440             }
441         };
442     }
443 }
444 
SubscribeMultiModal()445 bool RenderSwitch::SubscribeMultiModal()
446 {
447     if (isSubscribeMultimodal_) {
448         return true;
449     }
450     if (multiModalScene_.Invalid()) {
451         const auto pipelineContext = GetContext().Upgrade();
452         if (!pipelineContext) {
453             LOGW("the pipeline context is null");
454             return false;
455         }
456         const auto multimodalManager = pipelineContext->GetMultiModalManager();
457         if (!multimodalManager) {
458             LOGW("the multimodal manager is null");
459             return false;
460         }
461         const auto scene = multimodalManager->GetCurrentMultiModalScene();
462         switchEvent_ = VoiceEvent(component_->GetMultimodalProperties().voiceLabel, SceneLabel::SWITCH);
463         scene->SubscribeVoiceEvent(switchEvent_, multimodalSwitchEvent_);
464 
465         multiModalScene_ = scene;
466         isSubscribeMultimodal_ = true;
467     }
468     return true;
469 }
470 
UnSubscribeMultiModal()471 bool RenderSwitch::UnSubscribeMultiModal()
472 {
473     if (!isSubscribeMultimodal_) {
474         return true;
475     }
476     auto multiModalScene = multiModalScene_.Upgrade();
477     if (!multiModalScene) {
478         LOGE("fail to destroy multimodal event due to multiModalScene is null");
479         return false;
480     }
481     if (!switchEvent_.GetVoiceContent().empty()) {
482         multiModalScene->UnSubscribeVoiceEvent(switchEvent_);
483     }
484     isSubscribeMultimodal_ = false;
485     return true;
486 }
487 #endif
488 
OpenSwitch()489 void RenderSwitch::OpenSwitch()
490 {
491     if (!oldChecked_) {
492         HandleClick();
493     }
494 }
495 
CloseSwitch()496 void RenderSwitch::CloseSwitch()
497 {
498     if (oldChecked_) {
499         HandleClick();
500     }
501 }
502 
InitCurrentPointPosition()503 void RenderSwitch::InitCurrentPointPosition()
504 {
505     auto pointPadding = NormalizeToPx(pointPadding_);
506     if (needReverse_) {
507         currentPointOriginX_ =
508             oldChecked_ ? pointPadding : (switchSize_.Width() - pointPadding - rawPointSize_.Width());
509     } else {
510         currentPointOriginX_ =
511             oldChecked_ ? (switchSize_.Width() - pointPadding - rawPointSize_.Width()) : pointPadding;
512     }
513 }
514 
SetAccessibilityClickImpl()515 void RenderSwitch::SetAccessibilityClickImpl()
516 {
517     auto accessibilityNode = accessibilityNode_.Upgrade();
518     if (accessibilityNode) {
519         auto weakPtr = AceType::WeakClaim(AceType::RawPtr(clickRecognizer_));
520         accessibilityNode->AddSupportAction(AceAction::ACTION_CLICK);
521         accessibilityNode->SetClickableState(true);
522         accessibilityNode->SetCheckableState(true);
523         accessibilityNode->SetActionClickImpl([weakPtr]() {
524             auto click = weakPtr.Upgrade();
525             if (click) {
526                 click->OnAccepted();
527             }
528         });
529     }
530 }
531 
ProvideRestoreInfo()532 std::string RenderSwitch::ProvideRestoreInfo()
533 {
534     return std::to_string(checked_);
535 }
536 
ApplyRestoreInfo()537 void RenderSwitch::ApplyRestoreInfo()
538 {
539     if (GetRestoreInfo().empty()) {
540         return;
541     }
542     checked_ = static_cast<size_t>(StringUtils::StringToInt(GetRestoreInfo()));
543     SetRestoreInfo("");
544 }
545 
546 } // namespace OHOS::Ace
547