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