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