1 /*
2 * Copyright (c) 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_ng/pattern/radio/radio_pattern.h"
17
18 #include "base/utils/utils.h"
19 #include "core/components/checkable/checkable_theme.h"
20 #include "core/components_ng/pattern/radio/radio_paint_property.h"
21 #include "core/components_ng/pattern/stage/page_event_hub.h"
22 #include "core/components_ng/property/property.h"
23 #include "core/event/touch_event.h"
24 #include "core/pipeline_ng/pipeline_context.h"
25
26 namespace OHOS::Ace::NG {
27
28 namespace {
29 constexpr int FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO = 2;
30 } // namespace
31
OnAttachToFrameNode()32 void RadioPattern::OnAttachToFrameNode()
33 {
34 auto host = GetHost();
35 CHECK_NULL_VOID(host);
36 host->GetLayoutProperty()->UpdateAlignment(Alignment::CENTER);
37 }
38
OnDetachFromFrameNode(FrameNode * frameNode)39 void RadioPattern::OnDetachFromFrameNode(FrameNode* frameNode)
40 {
41 CHECK_NULL_VOID(frameNode);
42 auto pipelineContext = PipelineContext::GetCurrentContext();
43 CHECK_NULL_VOID(pipelineContext);
44 auto stageManager = pipelineContext->GetStageManager();
45 CHECK_NULL_VOID(stageManager);
46 auto pageNode = stageManager->GetLastPage();
47 CHECK_NULL_VOID(pageNode);
48 auto pageEventHub = pageNode->GetEventHub<NG::PageEventHub>();
49 CHECK_NULL_VOID(pageEventHub);
50 auto radioEventHub = frameNode->GetEventHub<NG::RadioEventHub>();
51 CHECK_NULL_VOID(radioEventHub);
52 pageEventHub->RemoveRadioFromGroup(radioEventHub->GetGroup(), frameNode->GetId());
53 }
54
OnModifyDone()55 void RadioPattern::OnModifyDone()
56 {
57 Pattern::OnModifyDone();
58 UpdateState();
59 auto host = GetHost();
60 CHECK_NULL_VOID(host);
61 auto pipeline = PipelineBase::GetCurrentContext();
62 CHECK_NULL_VOID(pipeline);
63 auto radioTheme = pipeline->GetTheme<RadioTheme>();
64 CHECK_NULL_VOID(radioTheme);
65 auto layoutProperty = host->GetLayoutProperty();
66 CHECK_NULL_VOID(layoutProperty);
67 MarginProperty margin;
68 margin.left = CalcLength(radioTheme->GetHotZoneHorizontalPadding().Value());
69 margin.right = CalcLength(radioTheme->GetHotZoneHorizontalPadding().Value());
70 margin.top = CalcLength(radioTheme->GetHotZoneVerticalPadding().Value());
71 margin.bottom = CalcLength(radioTheme->GetHotZoneVerticalPadding().Value());
72 auto& setMargin = layoutProperty->GetMarginProperty();
73 if (setMargin) {
74 if (setMargin->left.has_value()) {
75 margin.left = setMargin->left;
76 }
77 if (setMargin->right.has_value()) {
78 margin.right = setMargin->right;
79 }
80 if (setMargin->top.has_value()) {
81 margin.top = setMargin->top;
82 }
83 if (setMargin->bottom.has_value()) {
84 margin.bottom = setMargin->bottom;
85 }
86 }
87 layoutProperty->UpdateMargin(margin);
88 hotZoneHorizontalPadding_ = radioTheme->GetHotZoneHorizontalPadding();
89 hotZoneVerticalPadding_ = radioTheme->GetHotZoneVerticalPadding();
90 InitClickEvent();
91 InitTouchEvent();
92 InitMouseEvent();
93 auto focusHub = host->GetFocusHub();
94 CHECK_NULL_VOID(focusHub);
95 InitOnKeyEvent(focusHub);
96 }
97
InitClickEvent()98 void RadioPattern::InitClickEvent()
99 {
100 if (clickListener_) {
101 return;
102 }
103 auto host = GetHost();
104 CHECK_NULL_VOID(host);
105 auto gesture = host->GetOrCreateGestureEventHub();
106 CHECK_NULL_VOID(gesture);
107 auto clickCallback = [weak = WeakClaim(this)](GestureEvent& info) {
108 auto radioPattern = weak.Upgrade();
109 CHECK_NULL_VOID(radioPattern);
110 radioPattern->OnClick();
111 };
112 clickListener_ = MakeRefPtr<ClickEvent>(std::move(clickCallback));
113 gesture->AddClickEvent(clickListener_);
114 }
115
InitTouchEvent()116 void RadioPattern::InitTouchEvent()
117 {
118 if (touchListener_) {
119 return;
120 }
121 auto host = GetHost();
122 CHECK_NULL_VOID(host);
123 auto gesture = host->GetOrCreateGestureEventHub();
124 CHECK_NULL_VOID(gesture);
125 auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
126 auto radioPattern = weak.Upgrade();
127 CHECK_NULL_VOID(radioPattern);
128 if (info.GetTouches().front().GetTouchType() == TouchType::DOWN) {
129 radioPattern->OnTouchDown();
130 }
131 if (info.GetTouches().front().GetTouchType() == TouchType::UP ||
132 info.GetTouches().front().GetTouchType() == TouchType::CANCEL) {
133 radioPattern->OnTouchUp();
134 }
135 };
136 touchListener_ = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
137 gesture->AddTouchEvent(touchListener_);
138 }
139
InitMouseEvent()140 void RadioPattern::InitMouseEvent()
141 {
142 if (mouseEvent_) {
143 return;
144 }
145 auto host = GetHost();
146 CHECK_NULL_VOID(host);
147 auto gesture = host->GetOrCreateGestureEventHub();
148 CHECK_NULL_VOID(gesture);
149 auto eventHub = host->GetEventHub<RadioEventHub>();
150 auto inputHub = eventHub->GetOrCreateInputEventHub();
151
152 auto mouseTask = [weak = WeakClaim(this)](bool isHover) {
153 auto pattern = weak.Upgrade();
154 if (pattern) {
155 pattern->HandleMouseEvent(isHover);
156 }
157 };
158 mouseEvent_ = MakeRefPtr<InputEvent>(std::move(mouseTask));
159 inputHub->AddOnHoverEvent(mouseEvent_);
160 }
161
HandleMouseEvent(bool isHover)162 void RadioPattern::HandleMouseEvent(bool isHover)
163 {
164 isHover_ = isHover;
165 if (isHover) {
166 touchHoverType_ = TouchHoverAnimationType::HOVER;
167 } else {
168 touchHoverType_ = TouchHoverAnimationType::NONE;
169 }
170 auto host = GetHost();
171 CHECK_NULL_VOID(host);
172 host->MarkNeedRenderOnly();
173 }
174
OnClick()175 void RadioPattern::OnClick()
176 {
177 auto host = GetHost();
178 CHECK_NULL_VOID(host);
179 auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
180 CHECK_NULL_VOID(paintProperty);
181 bool check = false;
182 if (paintProperty->HasRadioCheck()) {
183 check = paintProperty->GetRadioCheckValue();
184 } else {
185 paintProperty->UpdateRadioCheck(false);
186 }
187 if (!preCheck_ && !check) {
188 paintProperty->UpdateRadioCheck(true);
189 UpdateState();
190 }
191 }
192
OnTouchDown()193 void RadioPattern::OnTouchDown()
194 {
195 if (isHover_) {
196 touchHoverType_ = TouchHoverAnimationType::HOVER_TO_PRESS;
197 } else {
198 touchHoverType_ = TouchHoverAnimationType::PRESS;
199 }
200 auto host = GetHost();
201 CHECK_NULL_VOID(host);
202 isTouch_ = true;
203 host->MarkNeedRenderOnly();
204 }
205
OnTouchUp()206 void RadioPattern::OnTouchUp()
207 {
208 if (isHover_) {
209 touchHoverType_ = TouchHoverAnimationType::PRESS_TO_HOVER;
210 } else {
211 touchHoverType_ = TouchHoverAnimationType::NONE;
212 }
213 auto host = GetHost();
214 CHECK_NULL_VOID(host);
215 isTouch_ = false;
216 host->MarkNeedRenderOnly();
217 }
218
CheckPageNode()219 void RadioPattern::CheckPageNode()
220 {
221 auto host = GetHost();
222 CHECK_NULL_VOID(host);
223 auto prePageId = GetPrePageId();
224 auto pipelineContext = PipelineContext::GetCurrentContext();
225 CHECK_NULL_VOID(pipelineContext);
226 auto stageManager = pipelineContext->GetStageManager();
227 CHECK_NULL_VOID(stageManager);
228 auto pageNode = stageManager->GetPageById(host->GetPageId());
229 CHECK_NULL_VOID(pageNode);
230 if (pageNode->GetId() != prePageId) {
231 auto eventHub = host->GetEventHub<RadioEventHub>();
232 CHECK_NULL_VOID(eventHub);
233 auto pageEventHub = pageNode->GetEventHub<NG::PageEventHub>();
234 CHECK_NULL_VOID(pageEventHub);
235 auto group = eventHub->GetGroup();
236
237 pageEventHub->AddRadioToGroup(group, host->GetId());
238 auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
239 CHECK_NULL_VOID(paintProperty);
240 bool check = false;
241 if (paintProperty->HasRadioCheck()) {
242 check = paintProperty->GetRadioCheckValue();
243 }
244 UpdateGroupCheckStatus(host, pageNode, check);
245 }
246 }
247
UpdateState()248 void RadioPattern::UpdateState()
249 {
250 auto host = GetHost();
251 CHECK_NULL_VOID(host);
252 auto eventHub = host->GetEventHub<RadioEventHub>();
253 CHECK_NULL_VOID(eventHub);
254
255 auto pipelineContext = PipelineContext::GetCurrentContext();
256 CHECK_NULL_VOID(pipelineContext);
257 auto stageManager = pipelineContext->GetStageManager();
258 CHECK_NULL_VOID(stageManager);
259 auto pageNode = stageManager->GetLastPage();
260 CHECK_NULL_VOID(pageNode);
261 auto pageEventHub = pageNode->GetEventHub<NG::PageEventHub>();
262 CHECK_NULL_VOID(pageEventHub);
263 auto preGroup = GetPreGroup();
264 auto group = eventHub->GetGroup();
265 if (!preGroup.has_value()) {
266 pageEventHub->AddRadioToGroup(group, host->GetId());
267 SetPrePageId(pageNode->GetId());
268 auto callback = [weak = WeakClaim(this)]() {
269 auto radio = weak.Upgrade();
270 if (radio) {
271 radio->CheckPageNode();
272 }
273 };
274 pipelineContext->AddBuildFinishCallBack(callback);
275 }
276 if (preGroup.has_value() && preGroup.value() != group) {
277 pageEventHub->RemoveRadioFromGroup(preGroup.value(), host->GetId());
278 pageEventHub->AddRadioToGroup(group, host->GetId());
279 SetPrePageId(pageNode->GetId());
280 isGroupChanged_ = true;
281 }
282 SetPreGroup(group);
283
284 auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
285 CHECK_NULL_VOID(paintProperty);
286
287 bool check = false;
288 if (paintProperty->HasRadioCheck()) {
289 check = paintProperty->GetRadioCheckValue();
290 /*
291 * Do not set isFirstCreated_ to false if the radio is set to true at creation time. The isFirstCreated_ is set
292 * to false in UpdateGroupCheckStatus because isFirstCreated_ is also required to determine if an onChange event
293 * needs to be triggered.
294 */
295 if (check) {
296 UpdateUIStatus(true);
297 isOnAnimationFlag_ = true;
298 } else {
299 // If the radio is set to false, set isFirstCreated_ to false.
300 isFirstCreated_ = false;
301 }
302 } else {
303 paintProperty->UpdateRadioCheck(false);
304 // If the radio check is not set, set isFirstCreated_ to false.
305 isFirstCreated_ = false;
306 }
307 if (preCheck_ != check || isGroupChanged_) {
308 UpdateGroupCheckStatus(host, pageNode, check);
309 }
310 preCheck_ = check;
311 isGroupChanged_ = false;
312 }
313
UpdateUncheckStatus(const RefPtr<FrameNode> & frameNode)314 void RadioPattern::UpdateUncheckStatus(const RefPtr<FrameNode>& frameNode)
315 {
316 auto radioPaintProperty = frameNode->GetPaintProperty<RadioPaintProperty>();
317 CHECK_NULL_VOID(radioPaintProperty);
318 radioPaintProperty->UpdateRadioCheck(false);
319 frameNode->MarkNeedRenderOnly();
320
321 if (preCheck_) {
322 auto radioEventHub = GetEventHub<RadioEventHub>();
323 CHECK_NULL_VOID(radioEventHub);
324 radioEventHub->UpdateChangeEvent(false);
325 isOnAnimationFlag_ = false;
326 }
327 preCheck_ = false;
328 }
329
UpdateGroupCheckStatus(const RefPtr<FrameNode> & frameNode,const RefPtr<FrameNode> & pageNode,bool check)330 void RadioPattern::UpdateGroupCheckStatus(
331 const RefPtr<FrameNode>& frameNode, const RefPtr<FrameNode>& pageNode, bool check)
332 {
333 frameNode->MarkNeedRenderOnly();
334
335 auto pageEventHub = pageNode->GetEventHub<NG::PageEventHub>();
336 CHECK_NULL_VOID(pageEventHub);
337
338 auto radioEventHub = GetEventHub<RadioEventHub>();
339 CHECK_NULL_VOID(radioEventHub);
340 if (check) {
341 pageEventHub->UpdateRadioGroupValue(radioEventHub->GetGroup(), frameNode->GetId());
342 } else {
343 auto radioPaintProperty = frameNode->GetPaintProperty<RadioPaintProperty>();
344 CHECK_NULL_VOID(radioPaintProperty);
345 radioPaintProperty->UpdateRadioCheck(check);
346 if (!isGroupChanged_) {
347 isOnAnimationFlag_ = false;
348 }
349 }
350
351 if (!isFirstCreated_) {
352 radioEventHub->UpdateChangeEvent(check);
353 }
354 }
355
UpdateUIStatus(bool check)356 void RadioPattern::UpdateUIStatus(bool check)
357 {
358 uiStatus_ = check ? UIStatus::SELECTED : UIStatus::UNSELECTED;
359 auto host = GetHost();
360 CHECK_NULL_VOID(host);
361 host->MarkNeedRenderOnly();
362 }
363
InitOnKeyEvent(const RefPtr<FocusHub> & focusHub)364 void RadioPattern::InitOnKeyEvent(const RefPtr<FocusHub>& focusHub)
365 {
366 auto getInnerPaintRectCallback = [wp = WeakClaim(this)](RoundRect& paintRect) {
367 auto pattern = wp.Upgrade();
368 if (pattern) {
369 pattern->GetInnerFocusPaintRect(paintRect);
370 }
371 };
372 focusHub->SetInnerFocusPaintRectCallback(getInnerPaintRectCallback);
373 }
374
GetInnerFocusPaintRect(RoundRect & paintRect)375 void RadioPattern::GetInnerFocusPaintRect(RoundRect& paintRect)
376 {
377 auto pipeline = PipelineBase::GetCurrentContext();
378 CHECK_NULL_VOID(pipeline);
379 auto radioTheme = pipeline->GetTheme<RadioTheme>();
380 CHECK_NULL_VOID(radioTheme);
381 auto focusPaintPadding = radioTheme->GetFocusPaintPadding().ConvertToPx();
382 float outCircleRadius = size_.Width() / 2 + focusPaintPadding;
383 float originX = offset_.GetX() - focusPaintPadding;
384 float originY = offset_.GetY() - focusPaintPadding;
385 float width = size_.Width() + 2 * focusPaintPadding;
386 float height = size_.Height() + 2 * focusPaintPadding;
387 paintRect.SetRect({ originX, originY, width, height });
388 paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_LEFT_POS, outCircleRadius, outCircleRadius);
389 paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_RIGHT_POS, outCircleRadius, outCircleRadius);
390 paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_LEFT_POS, outCircleRadius, outCircleRadius);
391 paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_RIGHT_POS, outCircleRadius, outCircleRadius);
392 }
393
GetFocusPattern() const394 FocusPattern RadioPattern::GetFocusPattern() const
395 {
396 auto pipeline = PipelineBase::GetCurrentContext();
397 CHECK_NULL_RETURN(pipeline, FocusPattern());
398 auto radioTheme = pipeline->GetTheme<RadioTheme>();
399 CHECK_NULL_RETURN(radioTheme, FocusPattern());
400 auto activeColor = radioTheme->GetActiveColor();
401 FocusPaintParam focusPaintParam;
402 focusPaintParam.SetPaintColor(activeColor);
403 return { FocusType::NODE, true, FocusStyleType::CUSTOM_REGION, focusPaintParam };
404 }
405
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig &)406 bool RadioPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& /*config*/)
407 {
408 auto geometryNode = dirty->GetGeometryNode();
409 offset_ = geometryNode->GetContentOffset();
410 size_ = geometryNode->GetContentSize();
411 if (!isUserSetResponseRegion_) {
412 AddHotZoneRect();
413 }
414 return true;
415 }
416
417 // Set the default hot zone for the component.
AddHotZoneRect()418 void RadioPattern::AddHotZoneRect()
419 {
420 hotZoneOffset_.SetX(offset_.GetX() - hotZoneHorizontalPadding_.ConvertToPx());
421 hotZoneOffset_.SetY(offset_.GetY() - hotZoneVerticalPadding_.ConvertToPx());
422 hotZoneSize_.SetWidth(
423 size_.Width() + FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO * hotZoneHorizontalPadding_.ConvertToPx());
424 hotZoneSize_.SetHeight(
425 size_.Height() + FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO * hotZoneVerticalPadding_.ConvertToPx());
426 DimensionRect hotZoneRegion;
427 hotZoneRegion.SetSize(DimensionSize(Dimension(hotZoneSize_.Width()), Dimension(hotZoneSize_.Height())));
428 hotZoneRegion.SetOffset(DimensionOffset(Dimension(hotZoneOffset_.GetX()), Dimension(hotZoneOffset_.GetY())));
429 auto host = GetHost();
430 CHECK_NULL_VOID(host);
431 auto gestureHub = host->GetOrCreateGestureEventHub();
432 CHECK_NULL_VOID(gestureHub);
433 gestureHub->SetResponseRegion(std::vector<DimensionRect>({ hotZoneRegion }));
434 }
435
RemoveLastHotZoneRect() const436 void RadioPattern::RemoveLastHotZoneRect() const
437 {
438 auto host = GetHost();
439 CHECK_NULL_VOID(host);
440 host->RemoveLastHotZoneRect();
441 }
442
ProvideRestoreInfo()443 std::string RadioPattern::ProvideRestoreInfo()
444 {
445 auto jsonObj = JsonUtil::Create(true);
446 auto radioPaintProperty = GetPaintProperty<RadioPaintProperty>();
447 CHECK_NULL_RETURN(radioPaintProperty, "");
448 jsonObj->Put("checked", radioPaintProperty->GetRadioCheck().value_or(false));
449 return jsonObj->ToString();
450 }
451
OnRestoreInfo(const std::string & restoreInfo)452 void RadioPattern::OnRestoreInfo(const std::string& restoreInfo)
453 {
454 auto radioPaintProperty = GetPaintProperty<RadioPaintProperty>();
455 CHECK_NULL_VOID(radioPaintProperty);
456 auto info = JsonUtil::ParseJsonString(restoreInfo);
457 if (!info->IsValid() || !info->IsObject()) {
458 return;
459 }
460 auto jsonChecked = info->GetValue("checked");
461 radioPaintProperty->UpdateRadioCheck(jsonChecked->GetBool());
462 OnModifyDone();
463 }
464 } // namespace OHOS::Ace::NG
465