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