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