[NUI] Refactor NUI Components (#1159)
[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                     Style.UnderLine.BackgroundColor.All = value;
232                     //RelayoutRequest();
233                 }
234             }
235         }
236
237         /// <summary>
238         /// Text point size in Tab.
239         /// </summary>
240         /// <since_tizen> 6 </since_tizen>
241         public float PointSize
242         {
243             get
244             {
245                 return Style.Text?.PointSize?.All ?? 0;
246             }
247             set
248             {
249                 //CreateTextAttributes();
250                 if (Style.Text.PointSize == null)
251                 {
252                     Style.Text.PointSize = new FloatSelector();
253                 }
254                 Style.Text.PointSize.All = value;
255                 //RelayoutRequest();
256             }
257         }
258
259         /// <summary>
260         /// Text font family in Tab.
261         /// </summary>
262         /// <since_tizen> 6 </since_tizen>
263         public string FontFamily
264         {
265             get
266             {
267                 return Style.Text?.FontFamily.All;
268             }
269             set
270             {
271                 //CreateTextAttributes();
272                 Style.Text.FontFamily.All = value;
273                 //RelayoutRequest();
274             }
275         }
276
277         /// <summary>
278         /// Text color in Tab.
279         /// </summary>
280         /// <since_tizen> 6 </since_tizen>
281         public Color TextColor
282         {
283             get
284             {
285                 return Style.Text?.TextColor?.All;
286             }
287             set
288             {
289                 //CreateTextAttributes();
290                 if (Style.Text.TextColor == null)
291                 {
292                     Style.Text.TextColor = new ColorSelector();
293                 }
294                 Style.Text.TextColor.All = value;
295                 //RelayoutRequest();
296             }
297         }
298
299         /// <summary>
300         /// Text color selector in Tab.
301         /// </summary>
302         /// <since_tizen> 6 </since_tizen>
303         public ColorSelector TextColorSelector
304         {
305             get
306             {
307                 return (ColorSelector)Style.Text.TextColor;
308             }
309             set
310             {
311                 if (value != null)
312                 {
313                     //CreateTextAttributes();
314                     Style.Text.TextColor = value.Clone() as ColorSelector;
315                     //RelayoutRequest();
316                 }
317             }
318         }
319
320         /// <summary>
321         /// Add tab item by item data. The added item will be added to end of all items automatically.
322         /// </summary>
323         /// <param name="itemData">Item data which will apply to tab item view.</param>
324         /// <since_tizen> 6 </since_tizen>
325         public void AddItem(TabItemData itemData)
326         {
327             AddItemByIndex(itemData, itemList.Count);
328         }
329
330         /// <summary>
331         /// Insert tab item by item data. The inserted item will be added to the special position by index automatically.
332         /// </summary>
333         /// <param name="itemData">Item data which will apply to tab item view.</param>
334         /// <param name="index">Position index where will be inserted.</param>
335         /// <since_tizen> 6 </since_tizen>
336         public void InsertItem(TabItemData itemData, int index)
337         {
338             AddItemByIndex(itemData, index);
339         }
340
341         /// <summary>
342         /// Delete tab item by index.
343         /// </summary>
344         /// <param name="itemIndex">Position index where will be deleted.</param>
345         /// <since_tizen> 6 </since_tizen>
346         public void DeleteItem(int itemIndex)
347         {
348             if(itemList == null || itemIndex < 0 || itemIndex >= itemList.Count)
349             {
350                 return;
351             }
352
353             if (curIndex > itemIndex || (curIndex == itemIndex && itemIndex == itemList.Count - 1))
354             {
355                 curIndex--;
356             }
357
358             Remove(itemList[itemIndex]);
359             itemList[itemIndex].Dispose();
360             itemList.RemoveAt(itemIndex);
361
362             UpdateItems();
363         }
364
365         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
366         [EditorBrowsable(EditorBrowsableState.Never)]
367         public override void ApplyStyle(ViewStyle viewStyle)
368         {
369             base.ApplyStyle(viewStyle);
370
371             TabStyle tabStyle = viewStyle as TabStyle;
372
373             if (null != tabStyle)
374             {
375                 if (null == underline)
376                 {
377                     underline = new View()
378                     {
379                         PositionUsesPivotPoint = true,
380                         ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft,
381                         PivotPoint = Tizen.NUI.PivotPoint.BottomLeft,
382                     };
383                     Add(underline);
384                     CreateUnderLineAnimation();
385                 }
386
387                 underline.ApplyStyle(Style.UnderLine);
388             }
389         }
390
391         /// <summary>
392         /// Dispose Tab and all children on it.
393         /// </summary>
394         /// <param name="type">Dispose type.</param>
395         /// <since_tizen> 6 </since_tizen>
396         protected override void Dispose(DisposeTypes type)
397         {
398             if (disposed)
399             {
400                 return;
401             }
402
403             if (type == DisposeTypes.Explicit)
404             {
405                 if(underlineAni != null)
406                 {
407                     if(underlineAni.State == Animation.States.Playing)
408                     {
409                         underlineAni.Stop();
410                     }
411                     underlineAni.Dispose();
412                     underlineAni = null;
413                 }
414                 Utility.Dispose(underline);
415                 if(itemList != null)
416                 {
417                     for(int i = 0; i < itemList.Count; i++)
418                     {
419                         Remove(itemList[i]);
420                         itemList[i].Dispose();
421                         itemList[i] = null;
422                     }
423                     itemList.Clear();
424                     itemList = null;
425                 }
426             }
427
428             base.Dispose(type);
429         }
430
431         /// <summary>
432         /// Update Tab by attributes.
433         /// </summary>
434         /// <since_tizen> 6 </since_tizen>
435         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
436         [EditorBrowsable(EditorBrowsableState.Never)]
437         protected override void OnUpdate()
438         {
439             LayoutChild();
440         }
441
442         /// <summary>
443         /// Get Tab attribues.
444         /// </summary>
445         /// <since_tizen> 6 </since_tizen>
446         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
447         [EditorBrowsable(EditorBrowsableState.Never)]
448         protected override ViewStyle GetViewStyle()
449         {
450             return new TabStyle();
451         }
452
453         /// <summary>
454         /// Theme change callback when theme is changed, this callback will be trigger.
455         /// </summary>
456         /// <since_tizen> 6 </since_tizen>
457         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
458         [EditorBrowsable(EditorBrowsableState.Never)]
459         protected override void OnThemeChangedEvent(object sender, StyleManager.ThemeChangeEventArgs e)
460         {
461             TabStyle tempAttributes = StyleManager.Instance.GetAttributes(style) as TabStyle;
462             if (tempAttributes != null)
463             {
464                 Style.CopyFrom(tempAttributes);
465             }
466         }
467
468         /// <summary>
469         /// Layout child in Tab and it can be override by user.
470         /// </summary>
471         /// <since_tizen> 6 </since_tizen>
472         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
473         [EditorBrowsable(EditorBrowsableState.Never)]
474         protected virtual void LayoutChild()
475         {
476             if (itemList == null)
477             {
478                 return;
479             }
480
481             int totalNum = itemList.Count;
482             if (totalNum == 0)
483             {
484                 return;
485             }
486
487             int preX = (int)Style.ItemPadding.Start;
488             int preW = 0;
489             int itemSpace = Style.ItemSpace;
490
491             if (LayoutDirection == ViewLayoutDirectionType.LTR)
492             {
493                 if (Style.UseTextNaturalSize == true)
494                 {
495                     for (int i = 0; i < totalNum; i++)
496                     {
497                         preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0);
498                         itemList[i].Position2D.X = preX;
499                         itemList[i].Size2D.Width = preW;
500                         preX = itemList[i].Position2D.X + preW + itemSpace;
501                         itemList[i].Index = i;
502                     }
503                 }
504                 else
505                 {
506                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
507                     for (int i = 0; i < totalNum; i++)
508                     {
509                         itemList[i].Position2D.X = preX;
510                         itemList[i].Size2D.Width = preW;
511                         preX = itemList[i].Position2D.X + preW + itemSpace;
512                         itemList[i].Index = i;
513                     }
514                 }
515             }
516             else
517             {
518                 preX = (int)Style.ItemPadding.End;
519                 if (Style.UseTextNaturalSize == true)
520                 {
521                     int w = Size2D.Width;
522                     for (int i = 0; i < totalNum; i++)
523                     {
524                         preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0);
525                         itemList[i].Position2D.X = w - preW - preX;
526                         itemList[i].Size2D.Width = preW;
527                         preX = w - itemList[i].Position2D.X + itemSpace;
528                         itemList[i].Index = i;
529                     }
530                 }
531                 else
532                 {
533                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
534                     for (int i = totalNum - 1; i >= 0; i--)
535                     {
536                         itemList[i].Position2D.X = preX;
537                         itemList[i].Size2D.Width = preW;
538                         preX = itemList[i].Position2D.X + preW + itemSpace;
539                         itemList[i].Index = i;
540                     }
541                 }
542             }
543             UpdateUnderLinePos();
544         }
545
546         private void Initialize()
547         {
548             LayoutDirectionChanged += OnLayoutDirectionChanged;
549         }
550
551         private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
552         {
553             LayoutChild();
554         }
555
556         private void AddItemByIndex(TabItemData itemData, int index)
557         {
558             int h = 0;
559             int topSpace = (int)Style.ItemPadding.Top;
560             if (Style.UnderLine != null && Style.UnderLine.Size != null)
561             {
562                 h = (int)Style.UnderLine.Size.Height;
563             }
564
565             Tab.TabItem item = new TabItem();
566             item.TextItem.ApplyStyle(Style.Text);
567
568             item.Text = itemData.Text;
569             item.Size2D.Height = Size2D.Height - h - topSpace;
570             item.Position2D.Y = topSpace;
571             item.TouchEvent += ItemTouchEvent;
572             Add(item);
573
574             if (index >= itemList.Count)
575             {
576                 itemList.Add(item);
577             }
578             else
579             {
580                 itemList.Insert(index, item);
581             }
582
583             UpdateItems();
584         }
585
586         private void UpdateItems()
587         {
588             LayoutChild();
589             if (itemList != null && curIndex >= 0 && curIndex < itemList.Count)
590             {
591                 itemList[curIndex].ControlState = ControlStates.Selected;
592                 UpdateUnderLinePos();
593             }
594             else
595             {
596                 if (underline != null)
597                 {
598                     underline.Hide();
599                 }
600             }
601         }
602
603         private void CreateUnderLineAttributes()
604         {
605             if (Style.UnderLine == null)
606             {
607                 Style.UnderLine = new ViewStyle()
608                 {
609                     PositionUsesPivotPoint = true,
610                     ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft,
611                     PivotPoint = Tizen.NUI.PivotPoint.BottomLeft,
612                 };
613             }
614         }
615
616         private void CreateUnderLineAnimation()
617         {
618             if (underlineAni == null)
619             {
620                 underlineAni = new Animation(aniTime);
621             }
622         }
623         
624         private void UpdateUnderLinePos()
625         {
626             if (underline == null || Style.UnderLine == null || Style.UnderLine.Size == null
627                 || itemList == null || itemList.Count <= 0)
628             {
629                 return;
630             }
631
632             Style.UnderLine.Size.Width = itemList[curIndex].Size2D.Width;
633
634             underline.Size2D = new Size2D(itemList[curIndex].Size2D.Width, (int)Style.UnderLine.Size.Height);
635             underline.BackgroundColor = Style.UnderLine.BackgroundColor.All;
636             if (isNeedAnimation)
637             {
638                 CreateUnderLineAnimation();
639                 if (underlineAni.State == Animation.States.Playing)
640                 {
641                     underlineAni.Stop();
642                 }
643                 underlineAni.Clear();
644                 underlineAni.AnimateTo(underline, "PositionX", itemList[curIndex].Position2D.X);
645                 underlineAni.Play();
646             }
647             else
648             {
649                 underline.Position2D.X = itemList[curIndex].Position2D.X;
650                 isNeedAnimation = true;
651             }
652
653             underline.Show();
654         }
655
656         private void UpdateSelectedItem(TabItem item)
657         {
658             if(item == null || curIndex == item.Index)
659             {
660                 return;
661             }
662
663             ItemChangedEventArgs e = new ItemChangedEventArgs
664             {
665                 PreviousIndex = curIndex,
666                 CurrentIndex = item.Index
667             };
668             ItemChangedEvent?.Invoke(this, e);
669
670             itemList[curIndex].ControlState = ControlStates.Normal;
671             curIndex = item.Index;
672             itemList[curIndex].ControlState = ControlStates.Selected;
673
674             UpdateUnderLinePos();
675         }
676
677         private bool ItemTouchEvent(object source, TouchEventArgs e)
678         {
679             TabItem item = source as TabItem;
680             if(item == null)
681             {
682                 return false;
683             }
684             PointStateType state = e.Touch.GetState(0);
685             if (state == PointStateType.Up)
686             {
687                 UpdateSelectedItem(item);
688             }
689
690             return true;
691         }
692
693         internal class TabItem : View
694         {
695             public TabItem() : base()
696             {
697                 TextItem = new TextLabel()
698                 {
699                     ParentOrigin = Tizen.NUI.ParentOrigin.Center,
700                     PivotPoint = Tizen.NUI.PivotPoint.Center,
701                     PositionUsesPivotPoint = true,
702                     WidthResizePolicy = ResizePolicyType.FillToParent,
703                     HeightResizePolicy = ResizePolicyType.FillToParent,
704                     HorizontalAlignment = HorizontalAlignment.Center,
705                     VerticalAlignment = VerticalAlignment.Center
706                 };
707                 Add(TextItem);
708             }
709
710             internal int Index
711             {
712                 get;
713                 set;
714             }
715
716             public string Text
717             {
718                 get
719                 {
720                     return TextItem.Text;
721                 }
722                 set
723                 {
724                     TextItem.Text = value;
725                 }
726             }
727
728             internal TextLabel TextItem
729             {
730                 get;
731                 set;
732             }
733         }
734
735         /// <summary>
736         /// TabItemData is a class to record all data which will be applied to Tab item.
737         /// </summary>
738         /// <since_tizen> 6 </since_tizen>
739         public class TabItemData
740         {
741             /// <summary>
742             /// Text string in tab item view.
743             /// </summary>
744             /// <since_tizen> 6 </since_tizen>
745             public string Text
746             {
747                 get;
748                 set;
749             }
750         }
751
752         /// <summary>
753         /// ItemChangedEventArgs is a class to record item change event arguments which will sent to user.
754         /// </summary>
755         /// <since_tizen> 6 </since_tizen>
756         public class ItemChangedEventArgs : EventArgs
757         {
758             /// <summary> Previous selected index of Tab </summary>
759             /// <since_tizen> 6 </since_tizen>
760             public int PreviousIndex;
761             /// <summary> Current selected index of Tab </summary>
762             /// <since_tizen> 6 </since_tizen>
763             public int CurrentIndex;
764         }
765     }
766 }