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