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/button/toggle_button_pattern.h"
17 
18 #include "base/memory/ace_type.h"
19 #include "base/utils/utils.h"
20 #include "core/common/recorder/node_data_cache.h"
21 #include "core/components/button/button_theme.h"
22 #include "core/components/common/properties/color.h"
23 #include "core/components/toggle/toggle_theme.h"
24 #include "core/components_ng/base/ui_node.h"
25 #include "core/components_ng/pattern/button/toggle_button_paint_property.h"
26 #include "core/components_ng/pattern/text/text_layout_property.h"
27 #include "core/components_ng/property/property.h"
28 #include "core/pipeline/pipeline_base.h"
29 
30 namespace OHOS::Ace::NG {
31 namespace {
32 const Color ITEM_FILL_COLOR = Color::TRANSPARENT;
33 constexpr int32_t TOUCH_DURATION = 100;
34 constexpr int32_t TYPE_TOUCH = 0;
35 constexpr int32_t TYPE_HOVER = 1;
36 constexpr int32_t TYPE_CANCEL = 2;
37 }
38 
OnAttachToFrameNode()39 void ToggleButtonPattern::OnAttachToFrameNode()
40 {
41     InitParameters();
42 }
43 
InitParameters()44 void ToggleButtonPattern::InitParameters()
45 {
46     auto pipeline = PipelineBase::GetCurrentContext();
47     CHECK_NULL_VOID(pipeline);
48     auto toggleTheme = pipeline->GetTheme<ToggleTheme>();
49     CHECK_NULL_VOID(toggleTheme);
50     checkedColor_ = toggleTheme->GetCheckedColor();
51     unCheckedColor_ = toggleTheme->GetBackgroundColor();
52     textMargin_ = toggleTheme->GetTextMargin();
53     buttonMargin_ = toggleTheme->GetButtonMargin();
54     buttonHeight_ = toggleTheme->GetButtonHeight();
55     buttonRadius_ = toggleTheme->GetButtonRadius();
56     textFontSize_ = toggleTheme->GetTextFontSize();
57     textColor_ = toggleTheme->GetTextColor();
58     disabledAlpha_ = toggleTheme->GetDisabledAlpha();
59     auto buttonTheme = pipeline->GetTheme<ButtonTheme>();
60     CHECK_NULL_VOID(buttonTheme);
61     clickedColor_ = buttonTheme->GetClickedColor();
62 }
63 
OnModifyDone()64 void ToggleButtonPattern::OnModifyDone()
65 {
66     auto host = GetHost();
67     CHECK_NULL_VOID(host);
68 
69     auto layoutProperty = host->GetLayoutProperty();
70     CHECK_NULL_VOID(layoutProperty);
71     if (layoutProperty->GetPositionProperty()) {
72         layoutProperty->UpdateAlignment(
73             layoutProperty->GetPositionProperty()->GetAlignment().value_or(Alignment::CENTER));
74     } else {
75         layoutProperty->UpdateAlignment(Alignment::CENTER);
76     }
77 
78     auto buttonPaintProperty = GetPaintProperty<ToggleButtonPaintProperty>();
79     CHECK_NULL_VOID(buttonPaintProperty);
80     if (!isOn_.has_value()) {
81         isOn_ = buttonPaintProperty->GetIsOnValue();
82     }
83     bool changed = false;
84     if (buttonPaintProperty->HasIsOn()) {
85         bool isOn = buttonPaintProperty->GetIsOnValue();
86         changed = isOn ^ isOn_.value();
87         isOn_ = isOn;
88     }
89     const auto& renderContext = host->GetRenderContext();
90     CHECK_NULL_VOID(renderContext);
91 
92     if (!UseContentModifier()) {
93         if (isOn_.value()) {
94             auto selectedColor = buttonPaintProperty->GetSelectedColor().value_or(checkedColor_);
95             renderContext->UpdateBackgroundColor(selectedColor);
96         } else {
97             auto bgColor = buttonPaintProperty->GetBackgroundColor().value_or(unCheckedColor_);
98             renderContext->UpdateBackgroundColor(bgColor);
99         }
100     }
101 
102     if (changed) {
103         auto toggleButtonEventHub = GetEventHub<ToggleButtonEventHub>();
104         CHECK_NULL_VOID(toggleButtonEventHub);
105         toggleButtonEventHub->UpdateChangeEvent(isOn_.value());
106     }
107     FireBuilder();
108     InitButtonAndText();
109     HandleEnabled();
110     InitClickEvent();
111     InitTouchEvent();
112     InitHoverEvent();
113     InitOnKeyEvent();
114     SetAccessibilityAction();
115 }
116 
SetAccessibilityAction()117 void ToggleButtonPattern::SetAccessibilityAction()
118 {
119     auto host = GetHost();
120     CHECK_NULL_VOID(host);
121     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
122     CHECK_NULL_VOID(accessibilityProperty);
123     accessibilityProperty->SetActionSelect([weakPtr = WeakClaim(this)]() {
124         const auto& pattern = weakPtr.Upgrade();
125         CHECK_NULL_VOID(pattern);
126         pattern->UpdateSelectStatus(true);
127     });
128 
129     accessibilityProperty->SetActionClearSelection([weakPtr = WeakClaim(this)]() {
130         const auto& pattern = weakPtr.Upgrade();
131         CHECK_NULL_VOID(pattern);
132         pattern->UpdateSelectStatus(false);
133     });
134     FireBuilder();
135 }
136 
UpdateSelectStatus(bool isSelected)137 void ToggleButtonPattern::UpdateSelectStatus(bool isSelected)
138 {
139     auto host = GetHost();
140     CHECK_NULL_VOID(host);
141     auto context = host->GetRenderContext();
142     CHECK_NULL_VOID(context);
143     MarkIsSelected(isSelected);
144     context->OnMouseSelectUpdate(isSelected, ITEM_FILL_COLOR, ITEM_FILL_COLOR);
145 }
146 
MarkIsSelected(bool isSelected)147 void ToggleButtonPattern::MarkIsSelected(bool isSelected)
148 {
149     if (isOn_ == isSelected) {
150         return;
151     }
152     isOn_ = isSelected;
153     auto eventHub = GetEventHub<ToggleButtonEventHub>();
154     CHECK_NULL_VOID(eventHub);
155     eventHub->UpdateChangeEvent(isSelected);
156     auto host = GetHost();
157     CHECK_NULL_VOID(host);
158     if (isSelected) {
159         eventHub->SetCurrentUIState(UI_STATE_SELECTED, isSelected);
160         host->OnAccessibilityEvent(AccessibilityEventType::SELECTED);
161     } else {
162         eventHub->SetCurrentUIState(UI_STATE_SELECTED, isSelected);
163         host->OnAccessibilityEvent(AccessibilityEventType::CHANGE);
164     }
165 }
166 
OnAfterModifyDone()167 void ToggleButtonPattern::OnAfterModifyDone()
168 {
169     auto host = GetHost();
170     CHECK_NULL_VOID(host);
171     auto inspectorId = host->GetInspectorId().value_or("");
172     if (!inspectorId.empty()) {
173         Recorder::NodeDataCache::Get().PutBool(host, inspectorId, isOn_.value_or(false));
174     }
175 }
176 
InitTouchEvent()177 void ToggleButtonPattern::InitTouchEvent()
178 {
179     if (touchListener_) {
180         return;
181     }
182     auto host = GetHost();
183     CHECK_NULL_VOID(host);
184     auto gesture = host->GetOrCreateGestureEventHub();
185     CHECK_NULL_VOID(gesture);
186     auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
187         auto buttonPattern = weak.Upgrade();
188         CHECK_NULL_VOID(buttonPattern);
189         if (info.GetTouches().front().GetTouchType() == TouchType::DOWN) {
190             TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "button touch down");
191             buttonPattern->OnTouchDown();
192         }
193         if (info.GetTouches().front().GetTouchType() == TouchType::UP ||
194             info.GetTouches().front().GetTouchType() == TouchType::CANCEL) {
195             TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "button touch up");
196             buttonPattern->OnTouchUp();
197         }
198     };
199     touchListener_ = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
200     gesture->AddTouchEvent(touchListener_);
201 }
202 
OnTouchDown()203 void ToggleButtonPattern::OnTouchDown()
204 {
205     isPress_ = true;
206     FireBuilder();
207     if (UseContentModifier()) {
208         return;
209     }
210     auto host = GetHost();
211     CHECK_NULL_VOID(host);
212     auto buttonEventHub = GetEventHub<ButtonEventHub>();
213     CHECK_NULL_VOID(buttonEventHub);
214     if (buttonEventHub->GetStateEffect()) {
215         auto renderContext = host->GetRenderContext();
216         CHECK_NULL_VOID(renderContext);
217         backgroundColor_ = renderContext->GetBackgroundColor().value_or(Color::TRANSPARENT);
218         if (isSetClickedColor_) {
219             // for user self-defined
220             renderContext->UpdateBackgroundColor(clickedColor_);
221             return;
222         }
223         // for system default
224         auto isNeedToHandleHoverOpacity = false;
225         AnimateTouchAndHover(renderContext, isNeedToHandleHoverOpacity ? TYPE_HOVER : TYPE_CANCEL, TYPE_TOUCH,
226             TOUCH_DURATION, isNeedToHandleHoverOpacity ? Curves::SHARP : Curves::FRICTION);
227     }
228 }
229 
OnTouchUp()230 void ToggleButtonPattern::OnTouchUp()
231 {
232     isPress_ = false;
233     FireBuilder();
234     if (UseContentModifier()) {
235         return;
236     }
237     auto host = GetHost();
238     CHECK_NULL_VOID(host);
239     auto buttonEventHub = GetEventHub<ButtonEventHub>();
240     CHECK_NULL_VOID(buttonEventHub);
241     if (buttonEventHub->GetStateEffect()) {
242         auto renderContext = host->GetRenderContext();
243         if (isSetClickedColor_) {
244             renderContext->UpdateBackgroundColor(backgroundColor_);
245             return;
246         }
247         if (buttonEventHub->IsEnabled()) {
248             auto isNeedToHandleHoverOpacity = false;
249             AnimateTouchAndHover(renderContext, TYPE_TOUCH, isNeedToHandleHoverOpacity ? TYPE_HOVER : TYPE_CANCEL,
250                 TOUCH_DURATION, isNeedToHandleHoverOpacity ? Curves::SHARP : Curves::FRICTION);
251         } else {
252             AnimateTouchAndHover(renderContext, TYPE_TOUCH, TYPE_CANCEL, TOUCH_DURATION, Curves::FRICTION);
253         }
254     }
255 }
256 
InitClickEvent()257 void ToggleButtonPattern::InitClickEvent()
258 {
259     if (clickListener_) {
260         return;
261     }
262     auto host = GetHost();
263     CHECK_NULL_VOID(host);
264     auto gesture = host->GetOrCreateGestureEventHub();
265     CHECK_NULL_VOID(gesture);
266     auto clickCallback = [weak = WeakClaim(this)](GestureEvent& info) {
267         auto buttonPattern = weak.Upgrade();
268         buttonPattern->OnClick();
269     };
270     clickListener_ = MakeRefPtr<ClickEvent>(std::move(clickCallback));
271     gesture->AddClickEvent(clickListener_);
272 }
273 
OnClick()274 void ToggleButtonPattern::OnClick()
275 {
276     if (UseContentModifier()) {
277         return;
278     }
279     auto host = GetHost();
280     CHECK_NULL_VOID(host);
281     auto paintProperty = host->GetPaintProperty<ToggleButtonPaintProperty>();
282     CHECK_NULL_VOID(paintProperty);
283     bool isLastSelected = false;
284     if (paintProperty->HasIsOn()) {
285         isLastSelected = paintProperty->GetIsOnValue();
286     }
287     const auto& renderContext = host->GetRenderContext();
288     CHECK_NULL_VOID(renderContext);
289     Color selectedColor;
290     auto buttonPaintProperty = host->GetPaintProperty<ToggleButtonPaintProperty>();
291     CHECK_NULL_VOID(buttonPaintProperty);
292     if (isLastSelected) {
293         selectedColor = buttonPaintProperty->GetBackgroundColor().value_or(unCheckedColor_);
294     } else {
295         selectedColor = buttonPaintProperty->GetSelectedColor().value_or(checkedColor_);
296     }
297     paintProperty->UpdateIsOn(!isLastSelected);
298     isOn_ = !isLastSelected;
299     renderContext->UpdateBackgroundColor(selectedColor);
300     auto buttonEventHub = GetEventHub<ToggleButtonEventHub>();
301     CHECK_NULL_VOID(buttonEventHub);
302     buttonEventHub->UpdateChangeEvent(!isLastSelected);
303     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
304 }
305 
InitButtonAndText()306 void ToggleButtonPattern::InitButtonAndText()
307 {
308     auto host = GetHost();
309     CHECK_NULL_VOID(host);
310     auto layoutProperty = host->GetLayoutProperty<ButtonLayoutProperty>();
311     CHECK_NULL_VOID(layoutProperty);
312 
313     auto renderContext = host->GetRenderContext();
314     CHECK_NULL_VOID(renderContext);
315     if (!renderContext->HasBorderRadius()) {
316         renderContext->UpdateBorderRadius({ buttonRadius_, buttonRadius_, buttonRadius_, buttonRadius_ });
317     }
318     if (!host->GetFirstChild()) {
319         return;
320     }
321     auto textNode = DynamicCast<FrameNode>(host->GetFirstChild());
322     CHECK_NULL_VOID(textNode);
323     auto textLayoutProperty = textNode->GetLayoutProperty<TextLayoutProperty>();
324     CHECK_NULL_VOID(textLayoutProperty);
325     if (textLayoutProperty->HasFontSize()) {
326         layoutProperty->UpdateFontSize(textLayoutProperty->GetFontSizeValue(textFontSize_));
327     }
328     layoutProperty->UpdateLabel(textLayoutProperty->GetContentValue(""));
329     if (!textLayoutProperty->GetTextColor().has_value()) {
330         textLayoutProperty->UpdateTextColor(textColor_);
331     }
332 
333     if (!textLayoutProperty->GetMarginProperty()) {
334         MarginProperty margin;
335         margin.left = CalcLength(textMargin_.ConvertToPx());
336         margin.right = CalcLength(textMargin_.ConvertToPx());
337         textLayoutProperty->UpdateMargin(margin);
338     }
339     textNode->MarkModifyDone();
340     textNode->MarkDirtyNode();
341 }
342 
InitOnKeyEvent()343 void ToggleButtonPattern::InitOnKeyEvent()
344 {
345     auto host = GetHost();
346     CHECK_NULL_VOID(host);
347     auto focusHub = host->GetOrCreateFocusHub();
348     auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
349         auto pattern = wp.Upgrade();
350         if (!pattern) {
351             return false;
352         }
353         return pattern->OnKeyEvent(event);
354     };
355     focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
356 }
357 
OnKeyEvent(const KeyEvent & event)358 bool ToggleButtonPattern::OnKeyEvent(const KeyEvent& event)
359 {
360     if (event.action != KeyAction::DOWN) {
361         return false;
362     }
363     if (event.code == KeyCode::KEY_SPACE || event.code == KeyCode::KEY_ENTER) {
364         OnClick();
365         return true;
366     }
367     return false;
368 }
369 
ProvideRestoreInfo()370 std::string ToggleButtonPattern::ProvideRestoreInfo()
371 {
372     auto jsonObj = JsonUtil::Create(true);
373     jsonObj->Put("IsOn", isOn_.value_or(false));
374     return jsonObj->ToString();
375 }
376 
OnRestoreInfo(const std::string & restoreInfo)377 void ToggleButtonPattern::OnRestoreInfo(const std::string& restoreInfo)
378 {
379     auto toggleButtonPaintProperty = GetPaintProperty<ToggleButtonPaintProperty>();
380     CHECK_NULL_VOID(toggleButtonPaintProperty);
381     auto info = JsonUtil::ParseJsonString(restoreInfo);
382     if (!info->IsValid() || !info->IsObject()) {
383         return;
384     }
385     auto jsonIsOn = info->GetValue("IsOn");
386     toggleButtonPaintProperty->UpdateIsOn(jsonIsOn->GetBool());
387     OnModifyDone();
388 }
389 
OnColorConfigurationUpdate()390 void ToggleButtonPattern::OnColorConfigurationUpdate()
391 {
392     auto host = GetHost();
393     CHECK_NULL_VOID(host);
394     auto pipeline = PipelineBase::GetCurrentContext();
395     CHECK_NULL_VOID(pipeline);
396     auto toggleTheme = pipeline->GetTheme<ToggleTheme>();
397     CHECK_NULL_VOID(toggleTheme);
398     checkedColor_ = toggleTheme->GetCheckedColor();
399     unCheckedColor_ = toggleTheme->GetBackgroundColor();
400     OnModifyDone();
401 }
402 
SetButtonPress(bool isSelected)403 void ToggleButtonPattern::SetButtonPress(bool isSelected)
404 {
405     auto host = GetHost();
406     CHECK_NULL_VOID(host);
407     auto eventHub = host->GetEventHub<EventHub>();
408     CHECK_NULL_VOID(eventHub);
409     auto enabled = eventHub->IsEnabled();
410     if (!enabled) {
411         return;
412     }
413     auto paintProperty = host->GetPaintProperty<ToggleButtonPaintProperty>();
414     CHECK_NULL_VOID(paintProperty);
415     paintProperty->UpdateIsOn(isSelected);
416     OnModifyDone();
417 }
418 
FireBuilder()419 void ToggleButtonPattern::FireBuilder()
420 {
421     auto host = GetHost();
422     CHECK_NULL_VOID(host);
423     if (!toggleMakeFunc_.has_value()) {
424         auto children = host->GetChildren();
425         for (const auto& child : children) {
426             if (child->GetId() == nodeId_) {
427                 host->RemoveChildAndReturnIndex(child);
428                 host->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE);
429                 break;
430             }
431         }
432         return;
433     }
434     auto node = BuildContentModifierNode();
435     if (contentModifierNode_ == node) {
436         return;
437     }
438     auto renderContext = host->GetRenderContext();
439     CHECK_NULL_VOID(renderContext);
440     renderContext->UpdateBackgroundColor(Color::TRANSPARENT);
441     host->RemoveChildAndReturnIndex(contentModifierNode_);
442     contentModifierNode_ = node;
443     CHECK_NULL_VOID(contentModifierNode_);
444     nodeId_ = contentModifierNode_->GetId();
445     host->AddChild(contentModifierNode_, 0);
446     host->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE);
447 }
448 
BuildContentModifierNode()449 RefPtr<FrameNode> ToggleButtonPattern::BuildContentModifierNode()
450 {
451     if (!toggleMakeFunc_.has_value()) {
452         return nullptr;
453     }
454     auto host = GetHost();
455     CHECK_NULL_RETURN(host, nullptr);
456     auto eventHub = host->GetEventHub<EventHub>();
457     CHECK_NULL_RETURN(eventHub, nullptr);
458     auto enabled = eventHub->IsEnabled();
459     auto paintProperty = host->GetPaintProperty<ToggleButtonPaintProperty>();
460     CHECK_NULL_RETURN(paintProperty, nullptr);
461     bool isSelected = false;
462     if (paintProperty->HasIsOn()) {
463         isSelected = paintProperty->GetIsOnValue();
464     } else {
465         isSelected = false;
466     }
467     return (toggleMakeFunc_.value())(ToggleConfiguration(enabled, isSelected));
468 }
469 
SetToggleBuilderFunc(SwitchMakeCallback && toggleMakeFunc)470 void ToggleButtonPattern::SetToggleBuilderFunc(SwitchMakeCallback&& toggleMakeFunc)
471 {
472     if (toggleMakeFunc == nullptr) {
473         toggleMakeFunc_ = std::nullopt;
474         contentModifierNode_ = nullptr;
475         auto host = GetHost();
476         CHECK_NULL_VOID(host);
477         for (auto child : host->GetChildren()) {
478             auto childNode = DynamicCast<FrameNode>(child);
479             if (childNode) {
480                 childNode->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE);
481             }
482         }
483         OnModifyDone();
484         return;
485     }
486     toggleMakeFunc_ = std::move(toggleMakeFunc);
487 }
488 } // namespace OHOS::Ace::NG
489