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/option/option_pattern.h"
17
18 #include "base/memory/ace_type.h"
19 #include "base/utils/utils.h"
20 #include "core/components/common/layout/grid_system_manager.h"
21 #include "core/components/select/select_theme.h"
22 #include "core/components_ng/base/ui_node.h"
23 #include "core/components_ng/pattern/image/image_layout_property.h"
24 #include "core/components_ng/pattern/menu/menu_pattern.h"
25 #include "core/components_ng/pattern/option/option_paint_property.h"
26 #include "core/components_ng/pattern/option/option_view.h"
27 #include "core/components_ng/pattern/security_component/paste_button/paste_button_common.h"
28 #include "core/components_ng/pattern/security_component/paste_button/paste_button_model_ng.h"
29 #include "core/components_ng/pattern/security_component/security_component_pattern.h"
30 #include "core/components_ng/pattern/security_component/security_component_layout_property.h"
31 #include "core/components_ng/pattern/text/text_layout_property.h"
32 #include "core/components_ng/property/property.h"
33 #include "core/event/touch_event.h"
34 #include "core/pipeline/pipeline_base.h"
35 #include "core/pipeline_ng/pipeline_context.h"
36
37 namespace OHOS::Ace::NG {
38 namespace {
39 constexpr Dimension MIN_OPTION_WIDTH = 56.0_vp;
40 constexpr Dimension OPTION_MARGIN = 8.0_vp;
41 constexpr int32_t COLUMN_NUM = 2;
42 } // namespace
43
OnAttachToFrameNode()44 void OptionPattern::OnAttachToFrameNode()
45 {
46 RegisterOnKeyEvent();
47 RegisterOnClick();
48 RegisterOnTouch();
49 RegisterOnHover();
50 }
51
OnModifyDone()52 void OptionPattern::OnModifyDone()
53 {
54 Pattern::OnModifyDone();
55 auto context = PipelineBase::GetCurrentContext();
56 CHECK_NULL_VOID(context);
57 textTheme_ = context->GetTheme<TextTheme>();
58 CHECK_NULL_VOID(textTheme_);
59 selectTheme_ = context->GetTheme<SelectTheme>();
60 CHECK_NULL_VOID(selectTheme_);
61
62 auto host = GetHost();
63 CHECK_NULL_VOID(host);
64 auto eventHub = host->GetEventHub<OptionEventHub>();
65 CHECK_NULL_VOID(eventHub);
66 if (!eventHub->IsEnabled()) {
67 UpdatePasteFontColor(selectTheme_->GetDisabledMenuFontColor());
68 CHECK_NULL_VOID(text_);
69 text_->GetRenderContext()->UpdateForegroundColor(selectTheme_->GetDisabledMenuFontColor());
70 auto textLayoutProperty = text_->GetLayoutProperty<TextLayoutProperty>();
71 CHECK_NULL_VOID(textLayoutProperty);
72 textLayoutProperty->UpdateTextColor(selectTheme_->GetDisabledMenuFontColor());
73 text_->MarkModifyDone();
74 } else {
75 UpdatePasteFontColor(selectTheme_->GetMenuFontColor());
76 }
77 SetAccessibilityAction();
78 }
79
UpdatePasteFontColor(const Color & fontColor)80 void OptionPattern::UpdatePasteFontColor(const Color& fontColor)
81 {
82 CHECK_NULL_VOID(pasteButton_);
83 auto property = pasteButton_->GetPaintProperty<SecurityComponentPaintProperty>();
84 CHECK_NULL_VOID(property);
85 property->UpdateFontColor(fontColor);
86 pasteButton_->MarkModifyDone();
87 }
88
OnSelectProcess()89 void OptionPattern::OnSelectProcess()
90 {
91 auto host = GetHost();
92 CHECK_NULL_VOID(host);
93 auto hub = host->GetEventHub<OptionEventHub>();
94 CHECK_NULL_VOID(hub);
95 auto JsAction = hub->GetJsCallback();
96 if (JsAction) {
97 LOGI("Option's callback executing");
98 JsAction();
99 }
100 auto onSelect = hub->GetOnSelect();
101 if (onSelect) {
102 LOGI("selecting option %d", index_);
103 onSelect(index_);
104 }
105 host->OnAccessibilityEvent(AccessibilityEventType::SELECTED);
106 // hide menu when option is clicked
107 auto pipeline = PipelineContext::GetCurrentContext();
108 CHECK_NULL_VOID(pipeline);
109 auto overlayManager = pipeline->GetOverlayManager();
110 CHECK_NULL_VOID(overlayManager);
111 auto menu = GetMenu().Upgrade();
112 CHECK_NULL_VOID(menu);
113 auto menuPattern = menu->GetPattern<MenuPattern>();
114 CHECK_NULL_VOID(menuPattern);
115 menuPattern->HideMenu();
116 }
117
PlayBgColorAnimation(bool isHoverChange)118 void OptionPattern::PlayBgColorAnimation(bool isHoverChange)
119 {
120 AnimationOption option = AnimationOption();
121 if (isHoverChange) {
122 option.SetDuration(selectTheme_->GetHoverAnimationDuration());
123 option.SetCurve(Curves::FRICTION);
124 } else {
125 option.SetDuration(selectTheme_->GetPressAnimationDuration());
126 option.SetCurve(Curves::SHARP);
127 }
128
129 AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
130 auto pattern = weak.Upgrade();
131 CHECK_NULL_VOID(pattern);
132 auto host = pattern->GetHost();
133 CHECK_NULL_VOID(host);
134 auto renderContext = host->GetRenderContext();
135 CHECK_NULL_VOID(renderContext);
136 renderContext->BlendBgColor(pattern->GetBgBlendColor());
137 });
138 }
139
RegisterOnClick()140 void OptionPattern::RegisterOnClick()
141 {
142 auto host = GetHost();
143 CHECK_NULL_VOID(host);
144 auto hub = host->GetEventHub<OptionEventHub>();
145
146 auto event = [weak = WeakClaim(this)](GestureEvent& /* info */) {
147 auto pattern = weak.Upgrade();
148 CHECK_NULL_VOID(pattern);
149 pattern->OnSelectProcess();
150 };
151 auto clickEvent = MakeRefPtr<ClickEvent>(std::move(event));
152
153 auto gestureHub = host->GetOrCreateGestureEventHub();
154 CHECK_NULL_VOID(gestureHub);
155 gestureHub->AddClickEvent(clickEvent);
156 }
157
RegisterOnTouch()158 void OptionPattern::RegisterOnTouch()
159 {
160 auto host = GetHost();
161 CHECK_NULL_VOID(host);
162 auto gestureHub = host->GetOrCreateGestureEventHub();
163 CHECK_NULL_VOID(gestureHub);
164
165 auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
166 auto pattern = weak.Upgrade();
167 CHECK_NULL_VOID(pattern);
168 pattern->OnPress(info);
169 };
170 auto touchEvent = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
171 gestureHub->AddTouchEvent(touchEvent);
172 }
173
RegisterOnHover()174 void OptionPattern::RegisterOnHover()
175 {
176 auto host = GetHost();
177 CHECK_NULL_VOID(host);
178 auto inputHub = host->GetOrCreateInputEventHub();
179 CHECK_NULL_VOID(inputHub);
180 auto mouseTask = [weak = WeakClaim(this)](bool isHover) {
181 auto pattern = weak.Upgrade();
182 CHECK_NULL_VOID(pattern);
183 pattern->OnHover(isHover);
184 };
185 auto mouseEvent = MakeRefPtr<InputEvent>(std::move(mouseTask));
186 inputHub->AddOnHoverEvent(mouseEvent);
187 }
188
RegisterOnKeyEvent()189 void OptionPattern::RegisterOnKeyEvent()
190 {
191 auto host = GetHost();
192 CHECK_NULL_VOID(host);
193 auto focusHub = host->GetOrCreateFocusHub();
194 CHECK_NULL_VOID(focusHub);
195 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
196 auto pattern = wp.Upgrade();
197 CHECK_NULL_RETURN(pattern, false);
198 return pattern->OnKeyEvent(event);
199 };
200 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
201 }
202
OnKeyEvent(const KeyEvent & event)203 bool OptionPattern::OnKeyEvent(const KeyEvent& event)
204 {
205 if (event.action != KeyAction::DOWN) {
206 return false;
207 }
208 if (event.code == KeyCode::KEY_ENTER) {
209 OnSelectProcess();
210 return true;
211 }
212 if ((event.code == KeyCode::KEY_MOVE_HOME || event.code == KeyCode::KEY_MOVE_END) && IsSelectOption()) {
213 return UpdateOptionFocus(event.code);
214 }
215 return false;
216 }
217
UpdateOptionFocus(KeyCode code)218 bool OptionPattern::UpdateOptionFocus(KeyCode code)
219 {
220 auto meunNode = GetMenu().Upgrade();
221 CHECK_NULL_RETURN(meunNode, false);
222 auto menuPattern = meunNode->GetPattern<MenuPattern>();
223 CHECK_NULL_RETURN(menuPattern, false);
224 auto options = menuPattern->GetOptions();
225 if (!options.empty()) {
226 auto optionNode = (code == KeyCode::KEY_MOVE_HOME) ? options.front() : options.back();
227 auto eventHub = optionNode->GetOrCreateFocusHub();
228 eventHub->RequestFocusImmediately();
229 return true;
230 }
231 return false;
232 }
233
OnPress(const TouchEventInfo & info)234 void OptionPattern::OnPress(const TouchEventInfo& info)
235 {
236 auto host = GetHost();
237 CHECK_NULL_VOID(host);
238 const auto& renderContext = host->GetRenderContext();
239 CHECK_NULL_VOID(renderContext);
240 auto props = GetPaintProperty<OptionPaintProperty>();
241 CHECK_NULL_VOID(props);
242 auto touchType = info.GetTouches().front().GetTouchType();
243
244 auto pipeline = PipelineBase::GetCurrentContext();
245 CHECK_NULL_VOID(pipeline);
246 auto theme = pipeline->GetTheme<SelectTheme>();
247 // enter press status
248 if (touchType == TouchType::DOWN) {
249 // change background color, update press status
250 SetBgBlendColor(theme->GetClickedColor());
251 PlayBgColorAnimation(false);
252
253 props->UpdatePress(true);
254 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
255 // disable next option node's divider
256 UpdateNextNodeDivider(false);
257 }
258 // leave press status
259 else if (touchType == TouchType::UP) {
260 if (IsHover()) {
261 SetBgBlendColor(theme->GetHoverColor());
262 } else {
263 SetBgBlendColor(Color::TRANSPARENT);
264 }
265 PlayBgColorAnimation(false);
266
267 props->UpdatePress(false);
268 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
269 UpdateNextNodeDivider(true);
270 }
271 }
272
OnHover(bool isHover)273 void OptionPattern::OnHover(bool isHover)
274 {
275 SetIsHover(isHover);
276
277 auto host = GetHost();
278 CHECK_NULL_VOID(host);
279 auto renderContext = host->GetRenderContext();
280 CHECK_NULL_VOID(renderContext);
281 auto props = GetPaintProperty<OptionPaintProperty>();
282 CHECK_NULL_VOID(props);
283 if (isHover) {
284 auto pipeline = PipelineContext::GetCurrentContext();
285 CHECK_NULL_VOID(pipeline);
286 auto theme = pipeline->GetTheme<SelectTheme>();
287 auto hoverColor = theme->GetHoverColor();
288 SetBgBlendColor(hoverColor);
289
290 props->UpdateHover(true);
291 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
292 UpdateNextNodeDivider(false);
293 } else {
294 SetBgBlendColor(Color::TRANSPARENT);
295
296 props->UpdateHover(false);
297 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
298 UpdateNextNodeDivider(true);
299 }
300 PlayBgColorAnimation();
301 }
302
UpdateNextNodeDivider(bool needDivider)303 void OptionPattern::UpdateNextNodeDivider(bool needDivider)
304 {
305 auto host = GetHost();
306 // find next option node from parent menuNode
307 CHECK_NULL_VOID(host);
308 auto parent = host->GetParent();
309 CHECK_NULL_VOID(parent);
310 auto nextNode = parent->GetChildAtIndex(index_ + 1);
311 if (nextNode) {
312 if (!InstanceOf<FrameNode>(nextNode)) {
313 LOGW("next optionNode is not a frameNode! type = %{public}s", nextNode->GetTag().c_str());
314 return;
315 }
316 auto props = DynamicCast<FrameNode>(nextNode)->GetPaintProperty<OptionPaintProperty>();
317 CHECK_NULL_VOID(props);
318 props->UpdateNeedDivider(needDivider);
319 nextNode->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
320 }
321 }
322
SetBgColor(const Color & color)323 void OptionPattern::SetBgColor(const Color& color)
324 {
325 auto renderContext = GetHost()->GetRenderContext();
326 CHECK_NULL_VOID(renderContext);
327 renderContext->UpdateBackgroundColor(color);
328 bgColor_ = color;
329 }
330
SetFontSize(const Dimension & value)331 void OptionPattern::SetFontSize(const Dimension& value)
332 {
333 CHECK_NULL_VOID(text_);
334 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
335 CHECK_NULL_VOID(props);
336 text_->MarkModifyDone();
337 CHECK_NULL_VOID(selectTheme_);
338 props->UpdateFontSize(value.IsNegative() ? selectTheme_->GetMenuFontSize() : value);
339 }
340
SetItalicFontStyle(const Ace::FontStyle & value)341 void OptionPattern::SetItalicFontStyle(const Ace::FontStyle& value)
342 {
343 CHECK_NULL_VOID(text_);
344 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
345 CHECK_NULL_VOID(props);
346 text_->MarkModifyDone();
347 props->UpdateItalicFontStyle(value);
348 }
349
SetFontWeight(const FontWeight & value)350 void OptionPattern::SetFontWeight(const FontWeight& value)
351 {
352 CHECK_NULL_VOID(text_);
353 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
354 CHECK_NULL_VOID(props);
355 text_->MarkModifyDone();
356 props->UpdateFontWeight(value);
357 }
358
SetFontFamily(const std::vector<std::string> & value)359 void OptionPattern::SetFontFamily(const std::vector<std::string>& value)
360 {
361 CHECK_NULL_VOID(text_);
362 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
363 CHECK_NULL_VOID(props);
364 text_->MarkModifyDone();
365 props->UpdateFontFamily(value);
366 }
367
SetFontColor(const Color & color)368 void OptionPattern::SetFontColor(const Color& color)
369 {
370 CHECK_NULL_VOID(text_);
371 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
372 CHECK_NULL_VOID(props);
373 text_->MarkModifyDone();
374 props->UpdateTextColor(color);
375 auto context = text_->GetRenderContext();
376 CHECK_NULL_VOID(context);
377 context->UpdateForegroundColor(color);
378 context->UpdateForegroundColorFlag(false);
379 context->ResetForegroundColorStrategy();
380 }
381
InspectorGetFont()382 std::string OptionPattern::InspectorGetFont()
383 {
384 CHECK_NULL_RETURN(text_, "");
385 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
386 CHECK_NULL_RETURN(props, "");
387 return props->InspectorGetTextFont();
388 }
389
GetBgColor()390 Color OptionPattern::GetBgColor()
391 {
392 auto pipeline = PipelineContext::GetCurrentContext();
393 CHECK_NULL_RETURN(pipeline, Color());
394 auto theme = pipeline->GetTheme<SelectTheme>();
395 CHECK_NULL_RETURN(theme, Color());
396 auto bgColor = Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN) ? Color::TRANSPARENT
397 : theme->GetBackgroundColor();
398 return bgColor_.value_or(bgColor);
399 }
400
GetFontSize()401 Dimension OptionPattern::GetFontSize()
402 {
403 CHECK_NULL_RETURN(text_, Dimension());
404 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
405 CHECK_NULL_RETURN(props, Dimension());
406 CHECK_NULL_RETURN(selectTheme_, Dimension());
407 auto defaultSize = selectTheme_->GetMenuFontSize();
408 return props->GetFontSizeValue(defaultSize);
409 }
410
GetItalicFontStyle()411 Ace::FontStyle OptionPattern::GetItalicFontStyle()
412 {
413 CHECK_NULL_RETURN(text_, Ace::FontStyle());
414 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
415 CHECK_NULL_RETURN(props, Ace::FontStyle());
416 auto defaultStyle = textTheme_->GetTextStyle().GetFontStyle();
417 return props->GetItalicFontStyleValue(defaultStyle);
418 }
419
GetFontWeight()420 FontWeight OptionPattern::GetFontWeight()
421 {
422 CHECK_NULL_RETURN(text_, FontWeight());
423 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
424 CHECK_NULL_RETURN(props, FontWeight());
425 auto defaultWeight = textTheme_->GetTextStyle().GetFontWeight();
426 return props->GetFontWeightValue(defaultWeight);
427 }
428
GetFontFamily()429 std::vector<std::string> OptionPattern::GetFontFamily()
430 {
431 CHECK_NULL_RETURN(text_, std::vector<std::string>());
432 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
433 CHECK_NULL_RETURN(props, std::vector<std::string>());
434 auto defaultFamily = textTheme_->GetTextStyle().GetFontFamilies();
435 return props->GetFontFamilyValue(defaultFamily);
436 }
437
GetFontColor()438 Color OptionPattern::GetFontColor()
439 {
440 CHECK_NULL_RETURN(text_, Color::TRANSPARENT);
441 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
442 CHECK_NULL_RETURN(props, Color::TRANSPARENT);
443 auto defaultColor = selectTheme_->GetMenuFontColor();
444 return props->GetTextColorValue(defaultColor);
445 }
446
GetText()447 std::string OptionPattern::GetText()
448 {
449 CHECK_NULL_RETURN(text_, std::string());
450 auto textProps = text_->GetLayoutProperty<TextLayoutProperty>();
451 CHECK_NULL_RETURN(textProps, std::string());
452 return textProps->GetContentValue();
453 }
454
UpdateText(const std::string & content)455 void OptionPattern::UpdateText(const std::string& content)
456 {
457 CHECK_NULL_VOID(text_);
458 auto props = text_->GetLayoutProperty<TextLayoutProperty>();
459 CHECK_NULL_VOID(props);
460 props->UpdateContent(content);
461 text_->MarkModifyDone();
462 text_->MarkDirtyNode();
463 }
464
UpdateIcon(const std::string & src)465 void OptionPattern::UpdateIcon(const std::string& src)
466 {
467 iconSrc_ = src;
468 auto host = GetHost();
469 CHECK_NULL_VOID(host);
470 RefPtr<FrameNode> row =
471 host->GetChildAtIndex(0) ? AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(0)) : nullptr;
472 CHECK_NULL_VOID(row);
473 if (src.empty()) {
474 row->RemoveChild(icon_); // it's safe even if icon_ is nullptr
475 row->MarkModifyDone();
476 row->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
477 icon_ = nullptr;
478 return;
479 }
480 if (!icon_) {
481 icon_ = OptionView::CreateIcon(src, row);
482 row->MarkModifyDone();
483 row->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
484 return;
485 }
486
487 auto props = icon_->GetLayoutProperty<ImageLayoutProperty>();
488 CHECK_NULL_VOID(props);
489 auto imageSrcInfo = props->GetImageSourceInfo();
490 CHECK_NULL_VOID(imageSrcInfo);
491 imageSrcInfo->SetSrc(src);
492 props->UpdateImageSourceInfo(imageSrcInfo.value());
493 icon_->MarkModifyDone();
494 icon_->MarkDirtyNode();
495 }
496
SetAccessibilityAction()497 void OptionPattern::SetAccessibilityAction()
498 {
499 auto host = GetHost();
500 CHECK_NULL_VOID(host);
501 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
502 CHECK_NULL_VOID(accessibilityProperty);
503 accessibilityProperty->SetActionSelect([weakPtr = WeakClaim(this)]() {
504 const auto& pattern = weakPtr.Upgrade();
505 CHECK_NULL_VOID(pattern);
506 pattern->OnSelectProcess();
507 });
508 }
509
GetSelectOptionWidth()510 float OptionPattern::GetSelectOptionWidth()
511 {
512 RefPtr<GridColumnInfo> columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
513 auto parent = columnInfo->GetParent();
514 CHECK_NULL_RETURN(parent, MIN_OPTION_WIDTH.ConvertToPx());
515 parent->BuildColumnWidth();
516 auto defaultWidth = static_cast<float>(columnInfo->GetWidth(COLUMN_NUM)) - OPTION_MARGIN.ConvertToPx();
517 auto optionNode = GetHost();
518 CHECK_NULL_RETURN(optionNode, MIN_OPTION_WIDTH.ConvertToPx());
519 float finalWidth = MIN_OPTION_WIDTH.ConvertToPx();
520
521 if (IsWidthModifiedBySelect()) {
522 auto optionPatintProperty = optionNode->GetPaintProperty<OptionPaintProperty>();
523 CHECK_NULL_RETURN(optionPatintProperty, MIN_OPTION_WIDTH.ConvertToPx());
524 auto selectmodifiedwidth = optionPatintProperty->GetSelectModifiedWidth();
525 finalWidth = selectmodifiedwidth.value();
526 } else {
527 finalWidth = defaultWidth;
528 }
529
530 if (finalWidth < MIN_OPTION_WIDTH.ConvertToPx()) {
531 finalWidth = defaultWidth;
532 }
533
534 return finalWidth;
535 }
536 } // namespace OHOS::Ace::NG
537