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