[NUI] Fixed TCT failed issues (#1177)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Tab.cs
1 /*
2  * Copyright(c) 2019 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 using System;
18 using System.Collections.Generic;
19 using Tizen.NUI.BaseComponents;
20 using System.ComponentModel;
21
22 namespace Tizen.NUI.Components
23 {
24     /// <summary>
25     /// Tab is one kind of common component, it can be used as menu label.
26     /// User can handle Tab by adding/inserting/deleting TabItem.
27     /// </summary>
28     /// <since_tizen> 6 </since_tizen>
29     public class Tab : Control
30     {
31         private const int aniTime = 100; // will be defined in const file later
32         private List<TabItem> itemList = new List<TabItem>();
33         private int curIndex = 0;
34         private View underline = null;
35         private Animation underlineAni = null;
36         private bool isNeedAnimation = false;
37         private Extents space;
38
39         /// <summary>
40         /// Creates a new instance of a Tab.
41         /// </summary>
42         /// <since_tizen> 6 </since_tizen>
43         public Tab() : base()
44         {
45             Initialize();
46         }
47
48         /// <summary>
49         /// Creates a new instance of a Tab with style.
50         /// </summary>
51         /// <param name="style">Create Tab by special style defined in UX.</param>
52         /// <since_tizen> 6 </since_tizen>
53         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
54         [EditorBrowsable(EditorBrowsableState.Never)]
55         public Tab(string style) : base(style)
56         {
57             Initialize();
58         }
59
60         /// <summary>
61         /// Creates a new instance of a Tab with style.
62         /// </summary>
63         /// <param name="style">Create Tab by style customized by user.</param>
64         /// <since_tizen> 6 </since_tizen>
65         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
66         [EditorBrowsable(EditorBrowsableState.Never)]
67         public Tab(TabStyle style) : base(style)
68         {
69             Initialize();
70         }
71
72         /// <summary>
73         /// An event for the item changed signal which can be used to subscribe or unsubscribe the event handler provided by the user.<br />
74         /// </summary>
75         /// <since_tizen> 6 </since_tizen>
76         public event EventHandler<ItemChangedEventArgs> ItemChangedEvent;
77
78         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
79         [EditorBrowsable(EditorBrowsableState.Never)]
80         public new TabStyle Style => ViewStyle as TabStyle;
81
82         /// <summary>
83         /// Selected item's index in Tab.
84         /// </summary>
85         /// <since_tizen> 6 </since_tizen>
86         public int SelectedItemIndex
87         {
88             get
89             {
90                 return curIndex;
91             }
92             set
93             {
94                 if (value < itemList.Count)
95                 {
96                     UpdateSelectedItem(itemList[value]);
97                 }
98             }
99         }
100
101         /// <summary>
102         /// Flag to decide if TabItem is adjusted by text's natural width.
103         /// If true, TabItem's width will be equal as text's natural width, if false, it will be decided by Tab's width and tab item count.
104         /// </summary>
105         /// <since_tizen> 6 </since_tizen>
106         public bool UseTextNaturalSize
107         {
108             get
109             {
110                 return Style.UseTextNaturalSize;
111             }
112             set
113             {
114                 Style.UseTextNaturalSize = value;
115                 RelayoutRequest();
116             }
117         }
118
119         /// <summary>
120         /// Gap between items.
121         /// </summary>
122         /// <since_tizen> 6 </since_tizen>
123         public int ItemSpace
124         {
125             get
126             {
127                 return Style.ItemSpace;
128             }
129             set
130             {
131                 Style.ItemSpace = value;
132                 RelayoutRequest();
133             }
134         }
135
136         /// <summary>
137         /// Space in Tab. Sequence as Left, Right, Top, Bottom
138         /// </summary>
139         /// <since_tizen> 6 </since_tizen>
140         public Extents Space
141         {
142             get
143             {
144                 return ItemPadding;
145             }
146             set
147             {
148                 ItemPadding = value;
149             }
150         }
151
152         /// <summary>
153         /// Item paddings in Tab. Sequence as Left, Right, Top, Bottom
154         /// </summary>
155         /// <since_tizen> 6 </since_tizen>
156         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
157         [EditorBrowsable(EditorBrowsableState.Never)]
158         public Extents ItemPadding
159         {
160             get
161             {
162                 return space;
163             }
164             set
165             {
166                 if(null != value)
167                 {
168                     Style.ItemPadding.CopyFrom(value);
169
170                     if (null == space)
171                     {
172                         space = new Extents((ushort start, ushort end, ushort top, ushort bottom) =>
173                         {
174                             Style.ItemPadding.Start = start;
175                             Style.ItemPadding.End = end;
176                             Style.ItemPadding.Top = top;
177                             Style.ItemPadding.Bottom = bottom;
178                             RelayoutRequest();
179                         }, value.Start, value.End, value.Top, value.Bottom);
180                     }
181                     else
182                     {
183                         space.CopyFrom(value);
184                     }
185
186                     RelayoutRequest();
187                 }
188             }
189         }
190
191         /// <summary>
192         /// UnderLine view's size in Tab.
193         /// </summary>
194         /// <since_tizen> 6 </since_tizen>
195         public Size UnderLineSize
196         {
197             get
198             {
199                 return Style.UnderLine?.Size;
200             }
201             set
202             {
203                 if (value != null)
204                 {
205                     //CreateUnderLineAttributes();
206                     Style.UnderLine.Size = value;
207                     //RelayoutRequest();
208                 }
209             }
210         }
211
212         /// <summary>
213         /// UnderLine view's background in Tab.
214         /// </summary>
215         /// <since_tizen> 6 </since_tizen>
216         public Color UnderLineBackgroundColor
217         {
218             get
219             {
220                 return Style.UnderLine?.BackgroundColor?.All;
221             }
222             set
223             {
224                 if (value != null)
225                 {
226                     //CreateUnderLineAttributes();
227                     if (Style.UnderLine.BackgroundColor == null)
228                     {
229                         Style.UnderLine.BackgroundColor = new ColorSelector();
230                     }
231                     if (Style.UnderLine.BackgroundColor != null)
232                     {
233                         Style.UnderLine.BackgroundColor.All = value;
234                     }
235                     //RelayoutRequest();
236                 }
237             }
238         }
239
240         /// <summary>
241         /// Text point size in Tab.
242         /// </summary>
243         /// <since_tizen> 6 </since_tizen>
244         public float PointSize
245         {
246             get
247             {
248                 return Style.Text?.PointSize?.All ?? 0;
249             }
250             set
251             {
252                 //CreateTextAttributes();
253                 if (Style.Text.PointSize == null)
254                 {
255                     Style.Text.PointSize = new FloatSelector();
256                 }
257                 if (Style.Text.PointSize != null)
258                 {
259                 Style.Text.PointSize.All = value;
260                 }
261                 //RelayoutRequest();
262             }
263         }
264
265         /// <summary>
266         /// Text font family in Tab.
267         /// </summary>
268         /// <since_tizen> 6 </since_tizen>
269         public string FontFamily
270         {
271             get
272             {
273                 return Style.Text?.FontFamily.All;
274             }
275             set
276             {
277                 //CreateTextAttributes();
278                 Style.Text.FontFamily.All = value;
279                 //RelayoutRequest();
280             }
281         }
282
283         /// <summary>
284         /// Text color in Tab.
285         /// </summary>
286         /// <since_tizen> 6 </since_tizen>
287         public Color TextColor
288         {
289             get
290             {
291                 return Style.Text?.TextColor?.All;
292             }
293             set
294             {
295                 //CreateTextAttributes();
296                 if (Style.Text.TextColor == null)
297                 {
298                     Style.Text.TextColor = new ColorSelector();
299                 }
300                 if (Style.Text.TextColor != null)
301                 {
302                     Style.Text.TextColor.All = value;
303                 }
304                 //RelayoutRequest();
305             }
306         }
307
308         private ColorSelector textColorSelector = new ColorSelector();
309         /// <summary>
310         /// Text color selector in Tab.
311         /// </summary>
312         /// <since_tizen> 6 </since_tizen>
313         public ColorSelector TextColorSelector
314         {
315             get
316             {
317                 return textColorSelector;
318             }
319             set
320             {
321                 textColorSelector.Clone(value);
322             }
323         }
324
325
326         /// <summary>
327         /// Add tab item by item data. The added item will be added to end of all items automatically.
328         /// </summary>
329         /// <param name="itemData">Item data which will apply to tab item view.</param>
330         /// <since_tizen> 6 </since_tizen>
331         public void AddItem(TabItemData itemData)
332         {
333             AddItemByIndex(itemData, itemList.Count);
334         }
335
336         /// <summary>
337         /// Insert tab item by item data. The inserted item will be added to the special position by index automatically.
338         /// </summary>
339         /// <param name="itemData">Item data which will apply to tab item view.</param>
340         /// <param name="index">Position index where will be inserted.</param>
341         /// <since_tizen> 6 </since_tizen>
342         public void InsertItem(TabItemData itemData, int index)
343         {
344             AddItemByIndex(itemData, index);
345         }
346
347         /// <summary>
348         /// Delete tab item by index.
349         /// </summary>
350         /// <param name="itemIndex">Position index where will be deleted.</param>
351         /// <since_tizen> 6 </since_tizen>
352         public void DeleteItem(int itemIndex)
353         {
354             if(itemList == null || itemIndex < 0 || itemIndex >= itemList.Count)
355             {
356                 return;
357             }
358
359             if (curIndex > itemIndex || (curIndex == itemIndex && itemIndex == itemList.Count - 1))
360             {
361                 curIndex--;
362             }
363
364             Remove(itemList[itemIndex]);
365             itemList[itemIndex].Dispose();
366             itemList.RemoveAt(itemIndex);
367
368             UpdateItems();
369         }
370
371         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
372         [EditorBrowsable(EditorBrowsableState.Never)]
373         public override void ApplyStyle(ViewStyle viewStyle)
374         {
375             base.ApplyStyle(viewStyle);
376
377             TabStyle tabStyle = viewStyle as TabStyle;
378
379             if (null != tabStyle)
380             {
381                 if (null == underline)
382                 {
383                     underline = new View()
384                     {
385                         PositionUsesPivotPoint = true,
386                         ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft,
387                         PivotPoint = Tizen.NUI.PivotPoint.BottomLeft,
388                     };
389                     Add(underline);
390                     CreateUnderLineAnimation();
391                 }
392
393                 underline.ApplyStyle(Style.UnderLine);
394             }
395         }
396
397         /// <summary>
398         /// Dispose Tab and all children on it.
399         /// </summary>
400         /// <param name="type">Dispose type.</param>
401         /// <since_tizen> 6 </since_tizen>
402         protected override void Dispose(DisposeTypes type)
403         {
404             if (disposed)
405             {
406                 return;
407             }
408
409             if (type == DisposeTypes.Explicit)
410             {
411                 if(underlineAni != null)
412                 {
413                     if(underlineAni.State == Animation.States.Playing)
414                     {
415                         underlineAni.Stop();
416                     }
417                     underlineAni.Dispose();
418                     underlineAni = null;
419                 }
420                 Utility.Dispose(underline);
421                 if(itemList != null)
422                 {
423                     for(int i = 0; i < itemList.Count; i++)
424                     {
425                         Remove(itemList[i]);
426                         itemList[i].Dispose();
427                         itemList[i] = null;
428                     }
429                     itemList.Clear();
430                     itemList = null;
431                 }
432             }
433
434             base.Dispose(type);
435         }
436
437         /// <summary>
438         /// Update Tab by attributes.
439         /// </summary>
440         /// <since_tizen> 6 </since_tizen>
441         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
442         [EditorBrowsable(EditorBrowsableState.Never)]
443         protected override void OnUpdate()
444         {
445             LayoutChild();
446         }
447
448         /// <summary>
449         /// Get Tab attribues.
450         /// </summary>
451         /// <since_tizen> 6 </since_tizen>
452         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
453         [EditorBrowsable(EditorBrowsableState.Never)]
454         protected override ViewStyle GetViewStyle()
455         {
456             return new TabStyle();
457         }
458
459         /// <summary>
460         /// Theme change callback when theme is changed, this callback will be trigger.
461         /// </summary>
462         /// <since_tizen> 6 </since_tizen>
463         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
464         [EditorBrowsable(EditorBrowsableState.Never)]
465         protected override void OnThemeChangedEvent(object sender, StyleManager.ThemeChangeEventArgs e)
466         {
467             TabStyle tempAttributes = StyleManager.Instance.GetAttributes(style) as TabStyle;
468             if (tempAttributes != null)
469             {
470                 Style.CopyFrom(tempAttributes);
471             }
472         }
473
474         /// <summary>
475         /// Layout child in Tab and it can be override by user.
476         /// </summary>
477         /// <since_tizen> 6 </since_tizen>
478         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
479         [EditorBrowsable(EditorBrowsableState.Never)]
480         protected virtual void LayoutChild()
481         {
482             if (itemList == null)
483             {
484                 return;
485             }
486
487             int totalNum = itemList.Count;
488             if (totalNum == 0)
489             {
490                 return;
491             }
492
493             int preX = (int)Style.ItemPadding.Start;
494             int preW = 0;
495             int itemSpace = Style.ItemSpace;
496
497             if (LayoutDirection == ViewLayoutDirectionType.LTR)
498             {
499                 if (Style.UseTextNaturalSize == true)
500                 {
501                     for (int i = 0; i < totalNum; i++)
502                     {
503                         preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0);
504                         itemList[i].Position2D.X = preX;
505                         itemList[i].Size2D.Width = preW;
506                         preX = itemList[i].Position2D.X + preW + itemSpace;
507                         itemList[i].Index = i;
508                     }
509                 }
510                 else
511                 {
512                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
513                     for (int i = 0; i < totalNum; i++)
514                     {
515                         itemList[i].Position2D.X = preX;
516                         itemList[i].Size2D.Width = preW;
517                         preX = itemList[i].Position2D.X + preW + itemSpace;
518                         itemList[i].Index = i;
519                     }
520                 }
521             }
522             else
523             {
524                 preX = (int)Style.ItemPadding.End;
525                 if (Style.UseTextNaturalSize == true)
526                 {
527                     int w = Size2D.Width;
528                     for (int i = 0; i < totalNum; i++)
529                     {
530                         preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0);
531                         itemList[i].Position2D.X = w - preW - preX;
532                         itemList[i].Size2D.Width = preW;
533                         preX = w - itemList[i].Position2D.X + itemSpace;
534                         itemList[i].Index = i;
535                     }
536                 }
537                 else
538                 {
539                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
540                     for (int i = totalNum - 1; i >= 0; i--)
541                     {
542                         itemList[i].Position2D.X = preX;
543                         itemList[i].Size2D.Width = preW;
544                         preX = itemList[i].Position2D.X + preW + itemSpace;
545                         itemList[i].Index = i;
546                     }
547                 }
548             }
549             UpdateUnderLinePos();
550         }
551
552         private void Initialize()
553         {
554             LayoutDirectionChanged += OnLayoutDirectionChanged;
555         }
556
557         private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
558         {
559             LayoutChild();
560         }
561
562         private void AddItemByIndex(TabItemData itemData, int index)
563         {
564             int h = 0;
565             int topSpace = (int)Style.ItemPadding.Top;
566             if (Style.UnderLine != null && Style.UnderLine.Size != null)
567             {
568                 h = (int)Style.UnderLine.Size.Height;
569             }
570
571             Tab.TabItem item = new TabItem();
572             item.TextItem.ApplyStyle(Style.Text);
573
574             item.Text = itemData.Text;
575             item.Size2D.Height = Size2D.Height - h - topSpace;
576             item.Position2D.Y = topSpace;
577             item.TouchEvent += ItemTouchEvent;
578             Add(item);
579
580             if (index >= itemList.Count)
581             {
582                 itemList.Add(item);
583             }
584             else
585             {
586                 itemList.Insert(index, item);
587             }
588
589             UpdateItems();
590         }
591
592         private void UpdateItems()
593         {
594             LayoutChild();
595             if (itemList != null && curIndex >= 0 && curIndex < itemList.Count)
596             {
597                 itemList[curIndex].ControlState = ControlStates.Selected;
598                 UpdateUnderLinePos();
599             }
600             else
601             {
602                 if (underline != null)
603                 {
604                     underline.Hide();
605                 }
606             }
607         }
608
609         private void CreateUnderLineAttributes()
610         {
611             if (Style.UnderLine == null)
612             {
613                 Style.UnderLine = new ViewStyle()
614                 {
615                     PositionUsesPivotPoint = true,
616                     ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft,
617                     PivotPoint = Tizen.NUI.PivotPoint.BottomLeft,
618                 };
619             }
620         }
621
622         private void CreateUnderLineAnimation()
623         {
624             if (underlineAni == null)
625             {
626                 underlineAni = new Animation(aniTime);
627             }
628         }
629         
630         private void UpdateUnderLinePos()
631         {
632             if (underline == null || Style.UnderLine == null || Style.UnderLine.Size == null
633                 || itemList == null || itemList.Count <= 0)
634             {
635                 return;
636             }
637
638             Style.UnderLine.Size.Width = itemList[curIndex].Size2D.Width;
639
640             underline.Size2D = new Size2D(itemList[curIndex].Size2D.Width, (int)Style.UnderLine.Size.Height);
641             underline.BackgroundColor = Style.UnderLine.BackgroundColor.All;
642             if (isNeedAnimation)
643             {
644                 CreateUnderLineAnimation();
645                 if (underlineAni.State == Animation.States.Playing)
646                 {
647                     underlineAni.Stop();
648                 }
649                 underlineAni.Clear();
650                 underlineAni.AnimateTo(underline, "PositionX", itemList[curIndex].Position2D.X);
651                 underlineAni.Play();
652             }
653             else
654             {
655                 underline.Position2D.X = itemList[curIndex].Position2D.X;
656                 isNeedAnimation = true;
657             }
658
659             underline.Show();
660         }
661
662         private void UpdateSelectedItem(TabItem item)
663         {
664             if(item == null || curIndex == item.Index)
665             {
666                 return;
667             }
668
669             ItemChangedEventArgs e = new ItemChangedEventArgs
670             {
671                 PreviousIndex = curIndex,
672                 CurrentIndex = item.Index
673             };
674             ItemChangedEvent?.Invoke(this, e);
675
676             itemList[curIndex].ControlState = ControlStates.Normal;
677             curIndex = item.Index;
678             itemList[curIndex].ControlState = ControlStates.Selected;
679
680             UpdateUnderLinePos();
681         }
682
683         private bool ItemTouchEvent(object source, TouchEventArgs e)
684         {
685             TabItem item = source as TabItem;
686             if(item == null)
687             {
688                 return false;
689             }
690             PointStateType state = e.Touch.GetState(0);
691             if (state == PointStateType.Up)
692             {
693                 UpdateSelectedItem(item);
694             }
695
696             return true;
697         }
698
699         internal class TabItem : View
700         {
701             public TabItem() : base()
702             {
703                 TextItem = new TextLabel()
704                 {
705                     ParentOrigin = Tizen.NUI.ParentOrigin.Center,
706                     PivotPoint = Tizen.NUI.PivotPoint.Center,
707                     PositionUsesPivotPoint = true,
708                     WidthResizePolicy = ResizePolicyType.FillToParent,
709                     HeightResizePolicy = ResizePolicyType.FillToParent,
710                     HorizontalAlignment = HorizontalAlignment.Center,
711                     VerticalAlignment = VerticalAlignment.Center
712                 };
713                 Add(TextItem);
714             }
715
716             internal int Index
717             {
718                 get;
719                 set;
720             }
721
722             public string Text
723             {
724                 get
725                 {
726                     return TextItem.Text;
727                 }
728                 set
729                 {
730                     TextItem.Text = value;
731                 }
732             }
733
734             internal TextLabel TextItem
735             {
736                 get;
737                 set;
738             }
739         }
740
741         /// <summary>
742         /// TabItemData is a class to record all data which will be applied to Tab item.
743         /// </summary>
744         /// <since_tizen> 6 </since_tizen>
745         public class TabItemData
746         {
747             /// <summary>
748             /// Text string in tab item view.
749             /// </summary>
750             /// <since_tizen> 6 </since_tizen>
751             public string Text
752             {
753                 get;
754                 set;
755             }
756         }
757
758         /// <summary>
759         /// ItemChangedEventArgs is a class to record item change event arguments which will sent to user.
760         /// </summary>
761         /// <since_tizen> 6 </since_tizen>
762         public class ItemChangedEventArgs : EventArgs
763         {
764             /// <summary> Previous selected index of Tab </summary>
765             /// <since_tizen> 6 </since_tizen>
766             public int PreviousIndex;
767             /// <summary> Current selected index of Tab </summary>
768             /// <since_tizen> 6 </since_tizen>
769             public int CurrentIndex;
770         }
771     }
772 }