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