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