• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2023 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_ng/pattern/toggle/switch_pattern.h"
17 
18 #include <cmath>
19 #include <cstdint>
20 
21 #include "base/memory/referenced.h"
22 #include "base/utils/utils.h"
23 #include "core/animation/curve.h"
24 #include "core/animation/curves.h"
25 #include "core/common/container.h"
26 #include "core/common/recorder/node_data_cache.h"
27 #include "core/components/checkable/checkable_theme.h"
28 #include "core/components_ng/pattern/toggle/switch_layout_algorithm.h"
29 #include "core/components_ng/pattern/toggle/switch_paint_property.h"
30 #include "core/components_ng/property/property.h"
31 #include "core/pipeline/pipeline_base.h"
32 
33 namespace OHOS::Ace::NG {
34 namespace {
35 constexpr int32_t DEFAULT_DURATION = 200;
36 const Color ITEM_FILL_COLOR = Color::TRANSPARENT;
37 } // namespace
OnAttachToFrameNode()38 void SwitchPattern::OnAttachToFrameNode()
39 {
40     auto host = GetHost();
41     CHECK_NULL_VOID(host);
42 }
43 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,bool skipMeasure,bool skipLayout)44 bool SwitchPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, bool skipMeasure, bool skipLayout)
45 {
46     if (skipMeasure || dirty->SkipMeasureContent()) {
47         return false;
48     }
49     if (isOn_.value_or(false)) {
50         currentOffset_ = GetSwitchWidth();
51     }
52 
53     auto layoutAlgorithmWrapper = DynamicCast<LayoutAlgorithmWrapper>(dirty->GetLayoutAlgorithm());
54     CHECK_NULL_RETURN(layoutAlgorithmWrapper, false);
55     auto switchLayoutAlgorithm = DynamicCast<SwitchLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
56     CHECK_NULL_RETURN(switchLayoutAlgorithm, false);
57 
58     auto height = switchLayoutAlgorithm->GetHeight();
59     auto width = switchLayoutAlgorithm->GetWidth();
60 
61     width_ = width;
62     height_ = height;
63     auto geometryNode = dirty->GetGeometryNode();
64     offset_ = geometryNode->GetContentOffset();
65     size_ = geometryNode->GetContentSize();
66     if (!isUserSetResponseRegion_) {
67         AddHotZoneRect();
68     }
69     return true;
70 }
71 
OnModifyDone()72 void SwitchPattern::OnModifyDone()
73 {
74     Pattern::OnModifyDone();
75     UpdateSwitchLayoutProperty();
76     UpdateSwitchPaintProperty();
77     InitClickEvent();
78     auto host = GetHost();
79     CHECK_NULL_VOID(host);
80     auto hub = host->GetEventHub<EventHub>();
81     CHECK_NULL_VOID(hub);
82     auto gestureHub = hub->GetOrCreateGestureEventHub();
83     CHECK_NULL_VOID(gestureHub);
84     InitPanEvent(gestureHub);
85     InitTouchEvent();
86     InitMouseEvent();
87     auto focusHub = host->GetFocusHub();
88     CHECK_NULL_VOID(focusHub);
89     InitOnKeyEvent(focusHub);
90     SetAccessibilityAction();
91 }
92 
UpdateSwitchPaintProperty()93 void SwitchPattern::UpdateSwitchPaintProperty()
94 {
95     auto host = GetHost();
96     CHECK_NULL_VOID(host);
97     auto switchPaintProperty = host->GetPaintProperty<SwitchPaintProperty>();
98     CHECK_NULL_VOID(switchPaintProperty);
99     auto geometryNode = host->GetGeometryNode();
100     CHECK_NULL_VOID(geometryNode);
101     if (!isOn_.has_value()) {
102         isOn_ = switchPaintProperty->GetIsOnValue(false);
103     }
104     auto isOn = switchPaintProperty->GetIsOnValue(false);
105     if (isOn != isOn_.value_or(false)) {
106         isOn_ = isOn;
107         OnChange();
108     }
109 }
110 
UpdateSwitchLayoutProperty()111 void SwitchPattern::UpdateSwitchLayoutProperty()
112 {
113     auto pipeline = PipelineBase::GetCurrentContext();
114     CHECK_NULL_VOID(pipeline);
115     auto switchTheme = pipeline->GetTheme<SwitchTheme>();
116     CHECK_NULL_VOID(switchTheme);
117     MarginProperty margin;
118     margin.left = CalcLength(switchTheme->GetHotZoneHorizontalPadding().Value());
119     margin.right = CalcLength(switchTheme->GetHotZoneHorizontalPadding().Value());
120     margin.top = CalcLength(switchTheme->GetHotZoneVerticalPadding().Value());
121     margin.bottom = CalcLength(switchTheme->GetHotZoneVerticalPadding().Value());
122     auto host = GetHost();
123     CHECK_NULL_VOID(host);
124     auto layoutProperty = host->GetLayoutProperty();
125     CHECK_NULL_VOID(layoutProperty);
126     direction_ = layoutProperty->GetLayoutDirection();
127     auto& setMargin = layoutProperty->GetMarginProperty();
128     if (setMargin) {
129         if (setMargin->left.has_value()) {
130             margin.left = setMargin->left;
131         }
132         if (setMargin->right.has_value()) {
133             margin.right = setMargin->right;
134         }
135         if (setMargin->top.has_value()) {
136             margin.top = setMargin->top;
137         }
138         if (setMargin->bottom.has_value()) {
139             margin.bottom = setMargin->bottom;
140         }
141     }
142     layoutProperty->UpdateMargin(margin);
143     hotZoneHorizontalPadding_ = switchTheme->GetHotZoneHorizontalPadding();
144     hotZoneVerticalPadding_ = switchTheme->GetHotZoneVerticalPadding();
145     if (layoutProperty->GetPositionProperty()) {
146         layoutProperty->UpdateAlignment(
147             layoutProperty->GetPositionProperty()->GetAlignment().value_or(Alignment::CENTER));
148     } else {
149         layoutProperty->UpdateAlignment(Alignment::CENTER);
150     }
151 }
152 
SetAccessibilityAction()153 void SwitchPattern::SetAccessibilityAction()
154 {
155     auto host = GetHost();
156     CHECK_NULL_VOID(host);
157     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
158     CHECK_NULL_VOID(accessibilityProperty);
159     accessibilityProperty->SetActionSelect([weakPtr = WeakClaim(this)]() {
160         const auto& pattern = weakPtr.Upgrade();
161         CHECK_NULL_VOID(pattern);
162         pattern->UpdateSelectStatus(true);
163     });
164 
165     accessibilityProperty->SetActionClearSelection([weakPtr = WeakClaim(this)]() {
166         const auto& pattern = weakPtr.Upgrade();
167         CHECK_NULL_VOID(pattern);
168         pattern->UpdateSelectStatus(false);
169     });
170 }
171 
UpdateSelectStatus(bool isSelected)172 void SwitchPattern::UpdateSelectStatus(bool isSelected)
173 {
174     auto host = GetHost();
175     CHECK_NULL_VOID(host);
176     auto context = host->GetRenderContext();
177     CHECK_NULL_VOID(context);
178     MarkIsSelected(isSelected);
179     context->OnMouseSelectUpdate(isSelected, ITEM_FILL_COLOR, ITEM_FILL_COLOR);
180 }
181 
MarkIsSelected(bool isSelected)182 void SwitchPattern::MarkIsSelected(bool isSelected)
183 {
184     if (isOn_ == isSelected) {
185         return;
186     }
187     isOn_ = isSelected;
188     auto eventHub = GetEventHub<SwitchEventHub>();
189     CHECK_NULL_VOID(eventHub);
190     eventHub->UpdateChangeEvent(isSelected);
191     auto host = GetHost();
192     CHECK_NULL_VOID(host);
193     eventHub->SetCurrentUIState(UI_STATE_SELECTED, isSelected);
194     host->OnAccessibilityEvent(AccessibilityEventType::COMPONENT_CHANGE);
195 }
196 
OnAfterModifyDone()197 void SwitchPattern::OnAfterModifyDone()
198 {
199     auto host = GetHost();
200     CHECK_NULL_VOID(host);
201     auto inspectorId = host->GetInspectorId().value_or("");
202     if (!inspectorId.empty()) {
203         Recorder::NodeDataCache::Get().PutBool(inspectorId, isOn_.value_or(false));
204     }
205 }
206 
GetCurve() const207 RefPtr<Curve> SwitchPattern::GetCurve() const
208 {
209     auto switchPaintProperty = GetPaintProperty<SwitchPaintProperty>();
210     CHECK_NULL_RETURN(switchPaintProperty, nullptr);
211     return switchPaintProperty->GetCurve().value_or(nullptr);
212 }
213 
GetDuration() const214 int32_t SwitchPattern::GetDuration() const
215 {
216     auto switchPaintProperty = GetPaintProperty<SwitchPaintProperty>();
217     CHECK_NULL_RETURN(switchPaintProperty, DEFAULT_DURATION);
218     return switchPaintProperty->GetDuration().value_or(DEFAULT_DURATION);
219 }
220 
OnChange()221 void SwitchPattern::OnChange()
222 {
223     auto host = GetHost();
224     CHECK_NULL_VOID(host);
225     auto switchPaintProperty = host->GetPaintProperty<SwitchPaintProperty>();
226     CHECK_NULL_VOID(switchPaintProperty);
227     switchPaintProperty->UpdateIsOn(isOn_.value_or(false));
228     UpdateChangeEvent();
229     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
230 }
231 
GetSwitchWidth() const232 float SwitchPattern::GetSwitchWidth() const
233 {
234     auto host = GetHost();
235     CHECK_NULL_RETURN(host, false);
236     auto geometryNode = host->GetGeometryNode();
237     CHECK_NULL_RETURN(geometryNode, false);
238     auto switchWidth = geometryNode->GetContentSize().Width() - geometryNode->GetContentSize().Height();
239     return switchWidth;
240 }
241 
GetSwitchContentOffsetX() const242 float SwitchPattern::GetSwitchContentOffsetX() const
243 {
244     auto host = GetHost();
245     CHECK_NULL_RETURN(host, 0.0f);
246     auto geometryNode = host->GetGeometryNode();
247     CHECK_NULL_RETURN(geometryNode, 0.0f);
248     return geometryNode->GetContentOffset().GetX();
249 }
250 
UpdateChangeEvent() const251 void SwitchPattern::UpdateChangeEvent() const
252 {
253     auto switchEventHub = GetEventHub<SwitchEventHub>();
254     CHECK_NULL_VOID(switchEventHub);
255     switchEventHub->UpdateChangeEvent(isOn_.value());
256 }
257 
OnClick()258 void SwitchPattern::OnClick()
259 {
260     isOn_ = !isOn_.value_or(false);
261     OnChange();
262     auto host = GetHost();
263     CHECK_NULL_VOID(host);
264     host->OnAccessibilityEvent(AccessibilityEventType::COMPONENT_CHANGE);
265 }
266 
OnTouchDown()267 void SwitchPattern::OnTouchDown()
268 {
269     if (isHover_) {
270         touchHoverType_ = TouchHoverAnimationType::HOVER_TO_PRESS;
271     } else {
272         touchHoverType_ = TouchHoverAnimationType::PRESS;
273     }
274     auto host = GetHost();
275     CHECK_NULL_VOID(host);
276     isTouch_ = true;
277     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
278 }
279 
OnTouchUp()280 void SwitchPattern::OnTouchUp()
281 {
282     if (isHover_) {
283         touchHoverType_ = TouchHoverAnimationType::PRESS_TO_HOVER;
284     } else {
285         touchHoverType_ = TouchHoverAnimationType::NONE;
286     }
287     auto host = GetHost();
288     CHECK_NULL_VOID(host);
289     isTouch_ = false;
290     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
291 }
292 
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)293 void SwitchPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
294 {
295     if (panEvent_) {
296         return;
297     }
298 
299     auto actionStartTask = [weak = WeakClaim(this)](const GestureEvent& info) {
300         auto pattern = weak.Upgrade();
301         CHECK_NULL_VOID(pattern);
302         if (info.GetInputEventType() == InputEventType::AXIS) {
303             return;
304         }
305         pattern->HandleDragStart();
306     };
307 
308     auto actionUpdateTask = [weak = WeakClaim(this)](const GestureEvent& info) {
309         auto pattern = weak.Upgrade();
310         CHECK_NULL_VOID(pattern);
311         pattern->HandleDragUpdate(info);
312     };
313 
314     auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
315         auto pattern = weak.Upgrade();
316         CHECK_NULL_VOID(pattern);
317         if (info.GetInputEventType() == InputEventType::AXIS) {
318             return;
319         }
320         pattern->HandleDragEnd();
321     };
322 
323     auto actionCancelTask = [weak = WeakClaim(this)]() {
324         auto pattern = weak.Upgrade();
325         CHECK_NULL_VOID(pattern);
326         pattern->HandleDragEnd();
327     };
328 
329     PanDirection panDirection;
330     panDirection.type = PanDirection::HORIZONTAL;
331 
332     panEvent_ = MakeRefPtr<PanEvent>(
333         std::move(actionStartTask), std::move(actionUpdateTask), std::move(actionEndTask), std::move(actionCancelTask));
334     gestureHub->AddPanEvent(panEvent_, panDirection, 1, DEFAULT_PAN_DISTANCE);
335 }
336 
InitClickEvent()337 void SwitchPattern::InitClickEvent()
338 {
339     if (clickListener_) {
340         return;
341     }
342     auto host = GetHost();
343     CHECK_NULL_VOID(host);
344     auto gesture = host->GetOrCreateGestureEventHub();
345     CHECK_NULL_VOID(gesture);
346     auto clickCallback = [weak = WeakClaim(this)](GestureEvent& info) {
347         auto switchPattern = weak.Upgrade();
348         CHECK_NULL_VOID(switchPattern);
349         switchPattern->OnClick();
350     };
351 
352     clickListener_ = MakeRefPtr<ClickEvent>(std::move(clickCallback));
353     gesture->AddClickEvent(clickListener_);
354 }
355 
InitTouchEvent()356 void SwitchPattern::InitTouchEvent()
357 {
358     if (touchListener_) {
359         return;
360     }
361     auto host = GetHost();
362     CHECK_NULL_VOID(host);
363     auto gesture = host->GetOrCreateGestureEventHub();
364     CHECK_NULL_VOID(gesture);
365     auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
366         auto switchPattern = weak.Upgrade();
367         CHECK_NULL_VOID(switchPattern);
368         if (info.GetTouches().front().GetTouchType() == TouchType::DOWN) {
369             switchPattern->OnTouchDown();
370         }
371         if (info.GetTouches().front().GetTouchType() == TouchType::UP ||
372             info.GetTouches().front().GetTouchType() == TouchType::CANCEL) {
373             switchPattern->OnTouchUp();
374         }
375     };
376     touchListener_ = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
377     gesture->AddTouchEvent(touchListener_);
378 }
379 
InitMouseEvent()380 void SwitchPattern::InitMouseEvent()
381 {
382     if (mouseEvent_) {
383         return;
384     }
385     auto host = GetHost();
386     CHECK_NULL_VOID(host);
387     auto gesture = host->GetOrCreateGestureEventHub();
388     CHECK_NULL_VOID(gesture);
389     auto eventHub = GetHost()->GetEventHub<SwitchEventHub>();
390     auto inputHub = eventHub->GetOrCreateInputEventHub();
391 
392     auto mouseTask = [weak = WeakClaim(this)](bool isHover) {
393         auto pattern = weak.Upgrade();
394         CHECK_NULL_VOID(pattern);
395         pattern->HandleMouseEvent(isHover);
396     };
397     mouseEvent_ = MakeRefPtr<InputEvent>(std::move(mouseTask));
398     inputHub->AddOnHoverEvent(mouseEvent_);
399 }
400 
InitOnKeyEvent(const RefPtr<FocusHub> & focusHub)401 void SwitchPattern::InitOnKeyEvent(const RefPtr<FocusHub>& focusHub)
402 {
403     auto getInnerPaintRectCallback = [wp = WeakClaim(this)](RoundRect& paintRect) {
404         auto pattern = wp.Upgrade();
405         if (pattern) {
406             pattern->GetInnerFocusPaintRect(paintRect);
407         }
408     };
409     focusHub->SetInnerFocusPaintRectCallback(getInnerPaintRectCallback);
410 }
411 
GetInnerFocusPaintRect(RoundRect & paintRect)412 void SwitchPattern::GetInnerFocusPaintRect(RoundRect& paintRect)
413 {
414     auto pipelineContext = PipelineBase::GetCurrentContext();
415     CHECK_NULL_VOID(pipelineContext);
416     auto switchTheme = pipelineContext->GetTheme<SwitchTheme>();
417     CHECK_NULL_VOID(switchTheme);
418     auto focusPaintPadding = switchTheme->GetFocusPaintPadding().ConvertToPx();
419 
420     auto height = height_ + focusPaintPadding * 2;
421     auto width = width_ + focusPaintPadding * 2;
422     auto radio = height / 2.0;
423     auto Rect = RectF(offset_.GetX() - focusPaintPadding, offset_.GetY() - focusPaintPadding, width, height);
424 
425     paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_LEFT_POS, radio, radio);
426     paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_RIGHT_POS, radio, radio);
427     paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_LEFT_POS, radio, radio);
428     paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_RIGHT_POS, radio, radio);
429     paintRect.SetRect(Rect);
430 }
431 
HandleMouseEvent(bool isHover)432 void SwitchPattern::HandleMouseEvent(bool isHover)
433 {
434     isHover_ = isHover;
435     if (isHover) {
436         touchHoverType_ = TouchHoverAnimationType::HOVER;
437     } else {
438         touchHoverType_ = TouchHoverAnimationType::NONE;
439     }
440     auto host = GetHost();
441     CHECK_NULL_VOID(host);
442     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
443 }
444 
HandleDragStart()445 void SwitchPattern::HandleDragStart()
446 {
447     isDragEvent_ = true;
448 }
449 
HandleDragUpdate(const GestureEvent & info)450 void SwitchPattern::HandleDragUpdate(const GestureEvent& info)
451 {
452     dragOffsetX_ = static_cast<float>(info.GetLocalLocation().GetX());
453     auto host = GetHost();
454     CHECK_NULL_VOID(host);
455     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
456 }
457 
HandleDragEnd()458 void SwitchPattern::HandleDragEnd()
459 {
460     auto mainSize = GetSwitchWidth();
461     auto contentOffset = GetSwitchContentOffsetX();
462     if ((direction_ == TextDirection::RTL &&
463         ((isOn_.value() && dragOffsetX_ - contentOffset > mainSize / 2) ||
464         (!isOn_.value() && dragOffsetX_ - contentOffset <= mainSize / 2))) ||
465         (direction_ != TextDirection::RTL &&
466         ((isOn_.value() && dragOffsetX_ - contentOffset < mainSize / 2) ||
467         (!isOn_.value() && dragOffsetX_ - contentOffset >= mainSize / 2)))) {
468         OnClick();
469     }
470     isDragEvent_ = false;
471     dragOffsetX_ = 0.0f;
472     auto host = GetHost();
473     CHECK_NULL_VOID(host);
474     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
475 }
476 
IsOutOfBoundary(double mainOffset) const477 bool SwitchPattern::IsOutOfBoundary(double mainOffset) const
478 {
479     return mainOffset < 0 || mainOffset > GetSwitchWidth();
480 }
481 
482 // Set the default hot zone for the component.
AddHotZoneRect()483 void SwitchPattern::AddHotZoneRect()
484 {
485     hotZoneOffset_.SetX(offset_.GetX() - hotZoneHorizontalPadding_.ConvertToPx());
486     hotZoneOffset_.SetY(offset_.GetY() - hotZoneVerticalPadding_.ConvertToPx());
487     hotZoneSize_.SetWidth(size_.Width() + 2 * hotZoneHorizontalPadding_.ConvertToPx());
488     hotZoneSize_.SetHeight(size_.Height() + 2 * hotZoneVerticalPadding_.ConvertToPx());
489     DimensionRect hotZoneRegion;
490     hotZoneRegion.SetSize(DimensionSize(Dimension(hotZoneSize_.Width()), Dimension(hotZoneSize_.Height())));
491     hotZoneRegion.SetOffset(DimensionOffset(Dimension(hotZoneOffset_.GetX()), Dimension(hotZoneOffset_.GetY())));
492     auto host = GetHost();
493     CHECK_NULL_VOID(host);
494     auto gestureHub = host->GetOrCreateGestureEventHub();
495     CHECK_NULL_VOID(gestureHub);
496     std::vector<DimensionRect> hotZoneRegions;
497     hotZoneRegions.emplace_back(hotZoneRegion);
498     gestureHub->SetResponseRegion(hotZoneRegions);
499 }
500 
RemoveLastHotZoneRect() const501 void SwitchPattern::RemoveLastHotZoneRect() const
502 {
503     auto host = GetHost();
504     CHECK_NULL_VOID(host);
505     host->RemoveLastHotZoneRect();
506 }
507 
ProvideRestoreInfo()508 std::string SwitchPattern::ProvideRestoreInfo()
509 {
510     auto jsonObj = JsonUtil::Create(true);
511     jsonObj->Put("IsOn", isOn_.value_or(false));
512     return jsonObj->ToString();
513 }
514 
OnRestoreInfo(const std::string & restoreInfo)515 void SwitchPattern::OnRestoreInfo(const std::string& restoreInfo)
516 {
517     auto switchPaintProperty = GetPaintProperty<SwitchPaintProperty>();
518     CHECK_NULL_VOID(switchPaintProperty);
519     auto info = JsonUtil::ParseJsonString(restoreInfo);
520     if (!info->IsValid() || !info->IsObject()) {
521         return;
522     }
523     auto jsonIsOn = info->GetValue("IsOn");
524     switchPaintProperty->UpdateIsOn(jsonIsOn->GetBool());
525     OnModifyDone();
526 }
527 
OnColorConfigurationUpdate()528 void SwitchPattern::OnColorConfigurationUpdate()
529 {
530     auto host = GetHost();
531     CHECK_NULL_VOID(host);
532     auto pipeline = PipelineBase::GetCurrentContext();
533     CHECK_NULL_VOID(pipeline);
534     auto switchTheme = pipeline->GetTheme<SwitchTheme>();
535     CHECK_NULL_VOID(switchTheme);
536     auto switchPaintProperty = host->GetPaintProperty<SwitchPaintProperty>();
537     CHECK_NULL_VOID(switchPaintProperty);
538     switchPaintProperty->UpdateSwitchPointColor(switchTheme->GetPointColor());
539 
540     host->MarkDirtyNode();
541     host->SetNeedCallChildrenUpdate(false);
542 }
543 } // namespace OHOS::Ace::NG
544