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