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