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/checkable/render_checkbox.h"
17 
18 #include "base/log/event_report.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr int32_t DEFUALT_CHECKBOX_ANIMATION_DURATION = 150;
24 constexpr double DEFAULT_MAX_CHECKBOX_SHAPE_SCALE = 1.0;
25 constexpr double DEFAULT_MID_CHECKBOX_SHAPE_SCALE = 0.5;
26 constexpr double DEFAULT_MIN_CHECKBOX_SHAPE_SCALE = 0.0;
27 
28 const std::vector<double> CHECKBOX_SCALE = {
29     DEFAULT_MAX_CHECKBOX_SHAPE_SCALE, DEFAULT_MID_CHECKBOX_SHAPE_SCALE, DEFAULT_MIN_CHECKBOX_SHAPE_SCALE
30 };
31 const std::vector<CheckableStatus> CHECKABLE_STATUS = {
32     CheckableStatus::ALL, CheckableStatus::PART, CheckableStatus::NONE
33 };
34 } // namespace
35 
Update(const RefPtr<Component> & component)36 void RenderCheckbox::Update(const RefPtr<Component>& component)
37 {
38     RenderCheckable::Update(component);
39     const auto& checkbox = AceType::DynamicCast<CheckboxComponent>(component);
40     if (!checkbox) {
41         LOGE("cast to checkbox component failed");
42         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
43         return;
44     }
45     component_ = checkbox;
46 
47     if (isDeclarative_) {
48         if (firstUpdate_) {
49             firstUpdate_ = false;
50         } else {
51             if (!component_->GetCheckboxList().empty() && component_->HasValue()) {
52                 bool value = component_->GetValue();
53                 component_->SetMember(value);
54                 component_->SetGroupValue(value ? CheckableStatus::ALL : CheckableStatus::NONE);
55             }
56         }
57         UpdateGroupStatus();
58         component_->SetGroupValue(CHECKABLE_STATUS[static_cast<int32_t>(status_)]);
59 
60         checkbox->SetGroupValueUpdateHandler([weak = AceType::WeakClaim(this)](CheckableStatus checked) {
61             auto renderCheckbox = weak.Upgrade();
62             if (renderCheckbox && renderCheckbox->UpdateGroupValue(checked)) {
63                 renderCheckbox->MarkNeedRender();
64             }
65         });
66         checkbox->SetItemValueUpdateHandler([weak = AceType::WeakClaim(this)](bool checked) {
67             auto renderCheckbox = weak.Upgrade();
68             if (renderCheckbox && renderCheckbox->UpdateItemValue(checked)) {
69                 renderCheckbox->MarkNeedRender();
70             }
71         });
72 
73         onGroupChange_ = AceAsyncEvent<void(const std::shared_ptr<BaseEventInfo>&)>::Create(
74             checkbox->GetOnGroupChange(), context_);
75     }
76 
77     if (!controller_) {
78         controller_ = CREATE_ANIMATOR(GetContext());
79         auto weak = AceType::WeakClaim(this);
80         controller_->AddStopListener(Animator::StatusCallback([weak]() {
81             auto checkBox = weak.Upgrade();
82             if (checkBox) {
83                 checkBox->OnAnimationStop();
84             }
85         }));
86     }
87 
88     auto theme = GetTheme<CheckboxTheme>();
89     if (theme) {
90         borderWidth_ = theme->GetBorderWidth();
91         borderRadius_ = theme->GetBorderRadius();
92         checkStroke_ = theme->GetCheckStroke();
93     }
94     if (checkbox->GetUpdateType() == UpdateType::ALL) {
95         checked_ = checkbox->GetValue();
96     }
97 
98     ApplyRestoreInfo();
99 
100     UpdateUIStatus();
101     UpdateAccessibilityAttr();
102     SetAccessibilityClickImpl();
103 }
104 
UpdateItemValue(const bool itemValue)105 bool RenderCheckbox::UpdateItemValue(const bool itemValue)
106 {
107     if (!(component_->GetCheckboxName().empty())) {
108         UpdateUIStatus();
109         UpdateAnimation();
110         controller_->Play();
111     } else {
112         return false;
113     }
114 
115     return true;
116 }
117 
UpdateGroupValue(const CheckableStatus groupValue)118 bool RenderCheckbox::UpdateGroupValue(const CheckableStatus groupValue)
119 {
120     if (!(component_->GetGroupName().empty())) {
121         UpdateGroupStatus();
122         if (onGroupChange_) {
123             std::vector<std::string> result;
124             component_->GetSelectedCheckBoxName(result);
125             onGroupChange_(std::make_shared<CheckboxGroupResult>(result, static_cast<int32_t>(status_)));
126         }
127         if (component_->GetGroupValue() != groupValue) {
128             component_->SetGroupValue(groupValue);
129             lastStatus_ = status_;
130             UpdateUIStatus();
131             UpdateAnimation();
132             controller_->Play();
133         } else {
134             return false;
135         }
136     } else {
137         return false;
138     }
139 
140     return true;
141 }
142 
UpdateAccessibilityAttr()143 void RenderCheckbox::UpdateAccessibilityAttr()
144 {
145     auto accessibilityNode = GetAccessibilityNode().Upgrade();
146     if (!accessibilityNode) {
147         return;
148     }
149     accessibilityNode->SetCheckedState(checked_);
150     if (accessibilityNode->GetClicked()) {
151         accessibilityNode->SetClicked(false);
152         auto context = context_.Upgrade();
153         if (context) {
154             AccessibilityEvent checkboxEvent;
155             checkboxEvent.nodeId = accessibilityNode->GetNodeId();
156             checkboxEvent.eventType = "click";
157             context->SendEventToAccessibility(checkboxEvent);
158         }
159     }
160 }
161 
HandleClick()162 void RenderCheckbox::HandleClick()
163 {
164     auto context = context_.Upgrade();
165     if (isDeclarative_) {
166         if (!(component_->GetGroupName().empty())) {
167             lastStatus_ = status_;
168             auto value = (component_->GetGroupValue() ==
169                 CheckableStatus::NONE) ? CheckableStatus::ALL : CheckableStatus::NONE;
170             component_->SetGroupValue(value);
171             component_->SetMember(value == CheckableStatus::ALL);
172             UpdateGroupStatus();
173 
174             if (onGroupChange_) {
175                 std::vector<std::string> result;
176                 component_->GetSelectedCheckBoxName(result);
177                 onGroupChange_(std::make_shared<CheckboxGroupResult>(result, static_cast<int32_t>(status_)));
178             }
179         } else if (!(component_->GetCheckboxName().empty())) {
180             component_->SetValue(!component_->GetValue());
181             if (component_->GetGroup()) {
182                 component_->GetGroup()->SetGroupStatus();
183             }
184         }
185     }
186     UpdateUIStatus();
187     UpdateAnimation();
188     MarkNeedRender();
189     if (controller_) {
190         controller_->Play();
191     }
192     auto accessibilityNode = accessibilityNode_.Upgrade();
193     if (accessibilityNode) {
194         accessibilityNode->SetCheckedState(checked_);
195     }
196 }
197 
UpdateAnimation()198 void RenderCheckbox::UpdateAnimation()
199 {
200     if (!controller_) {
201         LOGE("the controller is nullptr");
202         return;
203     }
204     double from = 0.0;
205     double to = 0.0;
206     if (!(component_->GetGroupName().empty())) {
207         from = CHECKBOX_SCALE[static_cast<int32_t>(lastStatus_)];
208         to = CHECKBOX_SCALE[static_cast<int32_t>(status_)];
209     } else if (!(component_->GetCheckboxName().empty())) {
210         if (component_->GetValue()) {
211             from = DEFAULT_MIN_CHECKBOX_SHAPE_SCALE;
212             to = DEFAULT_MAX_CHECKBOX_SHAPE_SCALE;
213         } else {
214             from = DEFAULT_MAX_CHECKBOX_SHAPE_SCALE;
215             to = DEFAULT_MIN_CHECKBOX_SHAPE_SCALE;
216         }
217     } else {
218         if (checked_) {
219             from = DEFAULT_MAX_CHECKBOX_SHAPE_SCALE;
220             to = DEFAULT_MIN_CHECKBOX_SHAPE_SCALE;
221         } else {
222             from = DEFAULT_MAX_CHECKBOX_SHAPE_SCALE;
223             to = DEFAULT_MIN_CHECKBOX_SHAPE_SCALE;
224         }
225     }
226 
227     if (translate_) {
228         controller_->RemoveInterpolator(translate_);
229     }
230     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::FRICTION);
231     auto weak = AceType::WeakClaim(this);
232     translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
233         auto checkBox = weak.Upgrade();
234         if (checkBox) {
235             checkBox->UpdateCheckBoxShape(value);
236         }
237     }));
238     controller_->SetDuration(DEFUALT_CHECKBOX_ANIMATION_DURATION);
239     controller_->AddInterpolator(translate_);
240 }
241 
UpdateCheckBoxShape(const double value)242 void RenderCheckbox::UpdateCheckBoxShape(const double value)
243 {
244     if (value < DEFAULT_MIN_CHECKBOX_SHAPE_SCALE || value > DEFAULT_MAX_CHECKBOX_SHAPE_SCALE) {
245         return;
246     }
247     shapeScale_ = value;
248     if (!(component_->GetGroupName().empty())) {
249         if (lastStatus_ == SelectStatus::ALL && status_ == SelectStatus::PART) {
250             uiStatus_ = UIStatus::ON_TO_PART;
251         } else if (lastStatus_ == SelectStatus::ALL && status_ == SelectStatus::NONE) {
252             uiStatus_ = UIStatus::ON_TO_OFF;
253         } else if (lastStatus_ == SelectStatus::PART && status_ == SelectStatus::ALL) {
254             uiStatus_ = UIStatus::PART_TO_ON;
255         } else if (lastStatus_ == SelectStatus::PART && status_ == SelectStatus::NONE) {
256             uiStatus_ = UIStatus::PART_TO_OFF;
257         } else if (lastStatus_ == SelectStatus::NONE && status_ == SelectStatus::ALL) {
258             uiStatus_ = UIStatus::OFF_TO_ON;
259         } else if (lastStatus_ == SelectStatus::NONE && status_ == SelectStatus::PART) {
260             uiStatus_ = UIStatus::OFF_TO_PART;
261         }
262     } else if (!(component_->GetCheckboxName().empty())) {
263         uiStatus_ = (component_->GetValue()) ? UIStatus::OFF_TO_ON : UIStatus::ON_TO_OFF;
264     } else {
265         uiStatus_ = checked_ ? UIStatus::ON_TO_OFF : UIStatus::OFF_TO_ON;
266     }
267 
268     MarkNeedRender();
269 }
270 
UpdateGroupStatus()271 void RenderCheckbox::UpdateGroupStatus()
272 {
273     if (!component_) {
274         return;
275     }
276 
277     auto checkboxList = component_->GetCheckboxList();
278     int count = 0;
279     isGroup_ = !checkboxList.empty();
280     if (isGroup_) {
281         for (auto& item : checkboxList) {
282             if (item->GetValue()) {
283                 count++;
284             }
285         }
286         if (count == (int)checkboxList.size()) {
287             status_ = SelectStatus::ALL;
288         } else if (count > 0 && (int)checkboxList.size() > count) {
289             status_ = SelectStatus::PART;
290         } else {
291             status_ = SelectStatus::NONE;
292         }
293     }
294 }
295 
OnAnimationStop()296 void RenderCheckbox::OnAnimationStop()
297 {
298     // after the animation stopped,we need to update the check status
299     RenderCheckable::HandleClick();
300     UpdateAccessibilityAttr();
301 }
302 
SetAccessibilityClickImpl()303 void RenderCheckbox::SetAccessibilityClickImpl()
304 {
305     auto accessibilityNode = accessibilityNode_.Upgrade();
306     if (accessibilityNode) {
307         auto weakPtr = AceType::WeakClaim(AceType::RawPtr(clickRecognizer_));
308         accessibilityNode->AddSupportAction(AceAction::ACTION_CLICK);
309         accessibilityNode->SetClickableState(true);
310         accessibilityNode->SetCheckableState(true);
311         accessibilityNode->SetActionClickImpl([weakPtr]() {
312             auto click = weakPtr.Upgrade();
313             if (click) {
314                 click->OnAccepted();
315             }
316         });
317     }
318 }
319 
ProvideRestoreInfo()320 std::string RenderCheckbox::ProvideRestoreInfo()
321 {
322     return std::to_string(checked_);
323 }
324 
ApplyRestoreInfo()325 void RenderCheckbox::ApplyRestoreInfo()
326 {
327     if (GetRestoreInfo().empty()) {
328         return;
329     }
330     checked_ = static_cast<size_t>(StringUtils::StringToInt(GetRestoreInfo()));
331     SetRestoreInfo("");
332 }
333 
334 } // namespace OHOS::Ace
335