[NUI] Checks the button area if GrabTouchAfterLeave is true.
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Button.Internal.cs
1 /*
2  * Copyright(c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Components.Extension;
23 using Tizen.NUI.Accessibility; // To use AccessibilityManager
24
25 namespace Tizen.NUI.Components
26 {
27     public partial class Button
28     {
29         private ImageView overlayImage;
30         private TextLabel buttonText;
31         private ImageView buttonIcon;
32         private Vector2 size;
33
34         private EventHandler<StateChangedEventArgs> stateChangeHandler;
35
36         private bool isPressed = false;
37         private bool styleApplied = false;
38
39         /// <summary>
40         /// Get accessibility name.
41         /// </summary>
42         [EditorBrowsable(EditorBrowsableState.Never)]
43         protected override string AccessibilityGetName()
44         {
45             return Text;
46         }
47
48         /// <summary>
49         /// Prevents from showing child widgets in AT-SPI tree.
50         /// </summary>
51         [EditorBrowsable(EditorBrowsableState.Never)]
52         protected override bool AccessibilityShouldReportZeroChildren()
53         {
54             return true;
55         }
56
57         /// <summary>
58         /// The ButtonExtension instance that is injected by ButtonStyle.
59         /// </summary>
60         [EditorBrowsable(EditorBrowsableState.Never)]
61         protected ButtonExtension Extension { get; set; }
62
63         /// <summary>
64         /// Creates Button's text part.
65         /// </summary>
66         /// <return>The created Button's text part.</return>
67         [EditorBrowsable(EditorBrowsableState.Never)]
68         protected virtual TextLabel CreateText()
69         {
70             return new TextLabel(new TextLabelStyle())
71             {
72                 HorizontalAlignment = HorizontalAlignment.Center,
73                 VerticalAlignment = VerticalAlignment.Center,
74                 AccessibilityHighlightable = false,
75             };
76         }
77
78         /// <summary>
79         /// Creates Button's icon part.
80         /// </summary>
81         /// <return>The created Button's icon part.</return>
82         [EditorBrowsable(EditorBrowsableState.Never)]
83         protected virtual ImageView CreateIcon()
84         {
85             return new ImageView()
86             {
87                 AccessibilityHighlightable = false
88             };
89         }
90
91         /// <summary>
92         /// Creates Button's overlay image part.
93         /// </summary>
94         /// <return>The created Button's overlay image part.</return>
95         [EditorBrowsable(EditorBrowsableState.Never)]
96         protected virtual ImageView CreateOverlayImage()
97         {
98             return new ImageView
99             {
100                 PositionUsesPivotPoint = true,
101                 ParentOrigin = NUI.ParentOrigin.Center,
102                 PivotPoint = NUI.PivotPoint.Center,
103                 WidthResizePolicy = ResizePolicyType.FillToParent,
104                 HeightResizePolicy = ResizePolicyType.FillToParent,
105                 AccessibilityHighlightable = false
106             };
107         }
108
109         /// <summary>
110         /// Called when the Button is Clicked by a user
111         /// </summary>
112         /// <param name="eventArgs">The click information.</param>
113         [EditorBrowsable(EditorBrowsableState.Never)]
114         protected virtual void OnClicked(ClickedEventArgs eventArgs)
115         {
116         }
117
118         /// <summary>
119         /// Get Button style.
120         /// </summary>
121         /// <returns>The default button style.</returns>
122         /// <since_tizen> 8 </since_tizen>
123         protected override ViewStyle CreateViewStyle()
124         {
125             return new ButtonStyle();
126         }
127
128         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
129         [EditorBrowsable(EditorBrowsableState.Never)]
130         protected override void OnUpdate()
131         {
132             base.OnUpdate();
133             Extension?.OnRelayout(this);
134         }
135
136         /// <inheritdoc/>
137         [EditorBrowsable(EditorBrowsableState.Never)]
138         protected override bool HandleControlStateOnTouch(Touch touch)
139         {
140             if (!IsEnabled || null == touch)
141             {
142                 return false;
143             }
144
145             PointStateType state = touch.GetState(0);
146
147             switch (state)
148             {
149                 case PointStateType.Down:
150                     isPressed = true;
151                     Extension?.SetTouchInfo(touch);
152                     UpdateState();
153                     return true;
154                 case PointStateType.Interrupted:
155                     isPressed = false;
156                     UpdateState();
157                     return true;
158                 case PointStateType.Up:
159                     {
160                         if (!isPressed)
161                         {
162                             return false;
163                         }
164
165                         isPressed = false;
166
167                         if (IsSelectable)
168                         {
169                             Extension?.SetTouchInfo(touch);
170                             IsSelected = !IsSelected;
171                         }
172                         else
173                         {
174                             Extension?.SetTouchInfo(touch);
175                             UpdateState();
176                         }
177
178                         ClickedEventArgs eventArgs = new ClickedEventArgs();
179                         OnClickedInternal(eventArgs, touch);
180
181                         return true;
182                     }
183                 default:
184                     break;
185             }
186             return base.HandleControlStateOnTouch(touch);
187         }
188
189         /// <inheritdoc/>
190         [EditorBrowsable(EditorBrowsableState.Never)]
191         protected override void OnEnabled(bool enabled)
192         {
193             base.OnEnabled(enabled);
194             UpdateState();
195         }
196
197         /// <summary>
198         /// Update Button State.
199         /// </summary>
200         /// <since_tizen> 6 </since_tizen>
201         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
202         [EditorBrowsable(EditorBrowsableState.Never)]
203         protected void UpdateState()
204         {
205             if (!styleApplied) return;
206
207             ControlState sourceState = ControlState;
208             ControlState targetState;
209
210             // Normal, Disabled
211             targetState = IsEnabled ? ControlState.Normal : ControlState.Disabled;
212
213             // Selected, DisabledSelected
214             if (IsSelected) targetState += ControlState.Selected;
215
216             // Pressed, PressedSelected
217             if (isPressed) targetState += ControlState.Pressed;
218
219             // Focused, FocusedPressed, FocusedPressedSelected, DisabledFocused, DisabledSelectedFocused
220             if (IsFocused) targetState += ControlState.Focused;
221
222             if (sourceState != targetState)
223             {
224                 ControlState = targetState;
225                 OnUpdate();
226
227                 StateChangedEventArgs e = new StateChangedEventArgs
228                 {
229                     PreviousState = ControlStatesExtension.FromControlStateClass(sourceState),
230                     CurrentState = ControlStatesExtension.FromControlStateClass(targetState)
231                 };
232                 stateChangeHandler?.Invoke(this, e);
233
234                 Extension?.OnControlStateChanged(this, new ControlStateChangedEventArgs(sourceState, targetState));
235             }
236         }
237
238         /// <summary>
239         /// Dispose Button and all children on it.
240         /// </summary>
241         /// <param name="type">Dispose type.</param>
242         /// <since_tizen> 6 </since_tizen>
243         protected override void Dispose(DisposeTypes type)
244         {
245             if (disposed)
246             {
247                 return;
248             }
249
250             if (type == DisposeTypes.Explicit)
251             {
252                 Extension?.OnDispose(this);
253
254                 if (buttonIcon != null)
255                 {
256                     Utility.Dispose(buttonIcon);
257                 }
258                 if (buttonText != null)
259                 {
260                     Utility.Dispose(buttonText);
261                 }
262                 if (overlayImage != null)
263                 {
264                     Utility.Dispose(overlayImage);
265                 }
266             }
267
268             base.Dispose(type);
269         }
270
271         /// <summary>
272         /// Initializes AT-SPI object.
273         /// </summary>
274         [EditorBrowsable(EditorBrowsableState.Never)]
275         public override void OnInitialize()
276         {
277             base.OnInitialize();
278             SetAccessibilityConstructor(Role.PushButton);
279
280             AccessibilityHighlightable = true;
281             EnableControlStatePropagation = true;
282
283             AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "Button");
284
285             buttonText = CreateText();
286             buttonIcon = CreateIcon();
287             LayoutItems();
288
289 #if PROFILE_MOBILE
290             Feedback = true;
291 #endif
292         }
293
294         /// <inheritdoc/>
295         [EditorBrowsable(EditorBrowsableState.Never)]
296         public override void OnRelayout(Vector2 size, RelayoutContainer container)
297         {
298             if (size == null) return;
299
300             if (size.Equals(this.size))
301             {
302                 return;
303             }
304
305             this.size = new Vector2(size);
306
307             UpdateSizeAndSpacing();
308         }
309
310         /// <inheritdoc/>
311         [EditorBrowsable(EditorBrowsableState.Never)]
312         protected override void OnControlStateChanged(ControlStateChangedEventArgs controlStateChangedInfo)
313         {
314             base.OnControlStateChanged(controlStateChangedInfo);
315
316             var stateEnabled = !controlStateChangedInfo.CurrentState.Contains(ControlState.Disabled);
317
318             if (IsEnabled != stateEnabled)
319             {
320                 IsEnabled = stateEnabled;
321             }
322
323             var statePressed = controlStateChangedInfo.CurrentState.Contains(ControlState.Pressed);
324
325             if (isPressed != statePressed)
326             {
327                 isPressed = statePressed;
328             }
329         }
330
331         /// <summary>
332         /// Put sub items (e.g. buttonText, buttonIcon) to the right place.
333         /// </summary>
334         [EditorBrowsable(EditorBrowsableState.Never)]
335         protected virtual void LayoutItems()
336         {
337             if (buttonIcon == null || buttonText == null)
338             {
339                 return;
340             }
341
342             buttonIcon.Unparent();
343             buttonText.Unparent();
344             overlayImage?.Unparent();
345
346 #pragma warning disable CA2000
347             Size2D cellPadding = String.IsNullOrEmpty(buttonText.Text) ? new Size2D(0, 0) : itemSpacing;
348 #pragma warning restore CA2000
349
350             if (IconRelativeOrientation == IconOrientation.Left)
351             {
352                 Layout = new LinearLayout()
353                 {
354                     LinearOrientation = LinearLayout.Orientation.Horizontal,
355                     HorizontalAlignment = itemHorizontalAlignment,
356                     VerticalAlignment = itemVerticalAlignment,
357                     CellPadding = cellPadding
358                 };
359
360                 Add(buttonIcon);
361                 Add(buttonText);
362             }
363             else if (IconRelativeOrientation == IconOrientation.Right)
364             {
365                 Layout = new LinearLayout()
366                 {
367                     LinearOrientation = LinearLayout.Orientation.Horizontal,
368                     HorizontalAlignment = itemHorizontalAlignment,
369                     VerticalAlignment = itemVerticalAlignment,
370                     CellPadding = cellPadding
371                 };
372
373                 Add(buttonText);
374                 Add(buttonIcon);
375             }
376             else if (IconRelativeOrientation == IconOrientation.Top)
377             {
378                 Layout = new LinearLayout()
379                 {
380                     LinearOrientation = LinearLayout.Orientation.Vertical,
381                     HorizontalAlignment = itemHorizontalAlignment,
382                     VerticalAlignment = itemVerticalAlignment,
383                     CellPadding = cellPadding
384                 };
385
386                 Add(buttonIcon);
387                 Add(buttonText);
388             }
389             else if (IconRelativeOrientation == IconOrientation.Bottom)
390             {
391                 Layout = new LinearLayout()
392                 {
393                     LinearOrientation = LinearLayout.Orientation.Vertical,
394                     HorizontalAlignment = itemHorizontalAlignment,
395                     VerticalAlignment = itemVerticalAlignment,
396                     CellPadding = cellPadding
397                 };
398
399                 Add(buttonText);
400                 Add(buttonIcon);
401             }
402
403             if (overlayImage != null)
404             {
405                 overlayImage.ExcludeLayouting = true;
406                 Add(overlayImage);
407             }
408         }
409
410         private void UpdateSizeAndSpacing()
411         {
412             if (size == null || buttonIcon == null || buttonText == null)
413             {
414                 return;
415             }
416
417             LinearLayout layout = Layout as LinearLayout;
418
419             if (layout == null)
420             {
421                 return;
422             }
423
424             float lengthWithoutText = 0;
425             Size2D cellPadding = null;
426             Extents iconMargin = buttonIcon.Margin ?? new Extents(0);
427             Extents textMargin = buttonText.Margin ?? new Extents(0);
428
429             if (buttonIcon.Size.Width != 0 && buttonIcon.Size.Height != 0)
430             {
431                 lengthWithoutText = buttonIcon.Size.Width;
432
433                 if (!String.IsNullOrEmpty(buttonText.Text))
434                 {
435                     cellPadding = itemSpacing;
436
437                     if (iconRelativeOrientation == IconOrientation.Left || iconRelativeOrientation == IconOrientation.Right)
438                     {
439                         lengthWithoutText += (itemSpacing?.Width ?? 0) + iconMargin.Start + iconMargin.End + textMargin.Start + textMargin.End;
440                     }
441                     else
442                     {
443                         lengthWithoutText += (itemSpacing?.Height ?? 0) + iconMargin.Top + iconMargin.Bottom + textMargin.Top + textMargin.Bottom;
444                     }
445                 }
446             }
447
448             layout.CellPadding = cellPadding ?? new Size2D(0, 0);
449
450             // If the button has fixed width and the text is not empty, the text should not exceed button boundary.
451             if (WidthSpecification != LayoutParamPolicies.WrapContent && !String.IsNullOrEmpty(buttonText.Text))
452             {
453                 buttonText.MaximumSize = new Size2D((int)Math.Max(size.Width - lengthWithoutText, Math.Max(buttonText.MinimumSize.Width, 1)), (int)size.Height);
454             }
455         }
456
457         private void OnClickedInternal(ClickedEventArgs eventArgs, Touch touch)
458         {
459             // If GrabTouchAfterLeave is true, Up will result in Finished rather than Interrupted even if it is out of the button area.
460             // So, it is necessary to check whether it is Up in the button area.
461             if (GrabTouchAfterLeave == true)
462             {
463                 Vector2 localPosition = touch.GetLocalPosition(0);
464                 if ((localPosition != null && Size != null &&
465                     0 <= localPosition.X && localPosition.X <= Size.Width &&
466                     0 <= localPosition.Y && localPosition.Y <= Size.Height) == false)
467                 {
468                     return;
469                 }
470             }
471             OnClickedInternal(eventArgs);
472         }
473
474         private void OnClickedInternal(ClickedEventArgs eventArgs)
475         {
476             Command?.Execute(CommandParameter);
477             OnClicked(eventArgs);
478             Extension?.OnClicked(this, eventArgs);
479
480             ClickEventArgs nestedEventArgs = new ClickEventArgs();
481             ClickEvent?.Invoke(this, nestedEventArgs);
482             Clicked?.Invoke(this, eventArgs);
483         }
484     }
485 }