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