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