[NUI.Components] Fix BackgroundImage doesn't works issue (#1240) (#1243)
[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> 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 style.
63         /// </summary>
64         /// <param name="style">Create Tab by style 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(TabStyle style) : base(style)
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         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public new TabStyle Style => ViewStyle as TabStyle;
82
83         /// <summary>
84         /// Selected item's index in Tab.
85         /// </summary>
86         /// <since_tizen> 6 </since_tizen>
87         public int SelectedItemIndex
88         {
89             get
90             {
91                 return curIndex;
92             }
93             set
94             {
95                 if (value < itemList.Count)
96                 {
97                     UpdateSelectedItem(itemList[value]);
98                 }
99             }
100         }
101
102         /// <summary>
103         /// Flag to decide if TabItem is adjusted by text's natural width.
104         /// 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.
105         /// </summary>
106         /// <since_tizen> 6 </since_tizen>
107         public bool UseTextNaturalSize
108         {
109             get
110             {
111                 return Style?.UseTextNaturalSize ?? false;
112             }
113             set
114             {
115                 if (null != Style)
116                 {
117                     Style.UseTextNaturalSize = value;
118                     RelayoutRequest();
119                 }
120             }
121         }
122
123         /// <summary>
124         /// Gap between items.
125         /// </summary>
126         /// <since_tizen> 6 </since_tizen>
127         public int ItemSpace
128         {
129             get
130             {
131                 return Style?.ItemSpace ?? 0;
132             }
133             set
134             {
135                 if (null != Style)
136                 {
137                     Style.ItemSpace = value;
138                     RelayoutRequest();
139                 }
140             }
141         }
142
143         /// <summary>
144         /// Space in Tab. Sequence as Left, Right, Top, Bottom
145         /// </summary>
146         /// <since_tizen> 6 </since_tizen>
147         public Extents Space
148         {
149             get
150             {
151                 return ItemPadding;
152             }
153             set
154             {
155                 ItemPadding = value;
156             }
157         }
158
159         /// <summary>
160         /// Item paddings in Tab. Sequence as Left, Right, Top, Bottom
161         /// </summary>
162         /// <since_tizen> 6 </since_tizen>
163         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
164         [EditorBrowsable(EditorBrowsableState.Never)]
165         public Extents ItemPadding
166         {
167             get
168             {
169                 return space;
170             }
171             set
172             {
173                 if(null != value && null != Style?.ItemPadding)
174                 {
175                     Style.ItemPadding.CopyFrom(value);
176
177                     if (null == space)
178                     {
179                         space = new Extents((ushort start, ushort end, ushort top, ushort bottom) =>
180                         {
181                             Style.ItemPadding.Start = start;
182                             Style.ItemPadding.End = end;
183                             Style.ItemPadding.Top = top;
184                             Style.ItemPadding.Bottom = bottom;
185                             RelayoutRequest();
186                         }, value.Start, value.End, value.Top, value.Bottom);
187                     }
188                     else
189                     {
190                         space.CopyFrom(value);
191                     }
192
193                     RelayoutRequest();
194                 }
195             }
196         }
197
198         /// <summary>
199         /// UnderLine view's size in Tab.
200         /// </summary>
201         /// <since_tizen> 6 </since_tizen>
202         public Size UnderLineSize
203         {
204             get
205             {
206                 return Style?.UnderLine?.Size;
207             }
208             set
209             {
210                 if (null != Style?.UnderLine)
211                 {
212                     Style.UnderLine.Size = value;
213                 }
214             }
215         }
216
217         /// <summary>
218         /// UnderLine view's background in Tab.
219         /// </summary>
220         /// <since_tizen> 6 </since_tizen>
221         public Color UnderLineBackgroundColor
222         {
223             get
224             {
225                 return Style?.UnderLine?.BackgroundColor?.All;
226             }
227             set
228             {
229                 if (null != Style?.UnderLine)
230                 {
231                     Style.UnderLine.BackgroundColor = value;
232                 }
233             }
234         }
235
236         /// <summary>
237         /// Text point size in Tab.
238         /// </summary>
239         /// <since_tizen> 6 </since_tizen>
240         public float PointSize
241         {
242             get
243             {
244                 return Style?.Text?.PointSize?.All ?? 0;
245             }
246             set
247             {
248                 if (null != Style?.Text)
249                 {
250                     Style.Text.PointSize = value;
251                 }
252             }
253         }
254
255         /// <summary>
256         /// Text font family in Tab.
257         /// </summary>
258         /// <since_tizen> 6 </since_tizen>
259         public string FontFamily
260         {
261             get
262             {
263                 return Style?.Text?.FontFamily?.All;
264             }
265             set
266             {
267                 if (null != Style?.Text)
268                 {
269                     Style.Text.FontFamily = value;
270                 }
271             }
272         }
273
274         /// <summary>
275         /// Text color in Tab.
276         /// </summary>
277         /// <since_tizen> 6 </since_tizen>
278         public Color TextColor
279         {
280             get
281             {
282                 return Style?.Text?.TextColor?.All;
283             }
284             set
285             {
286                 if (null != Style?.Text)
287                 {
288                     Style.Text.TextColor = value;
289                 }
290             }
291         }
292
293         private ColorSelector textColorSelector = new ColorSelector();
294         /// <summary>
295         /// Text color selector in Tab.
296         /// </summary>
297         /// <since_tizen> 6 </since_tizen>
298         public ColorSelector TextColorSelector
299         {
300             get
301             {
302                 return textColorSelector;
303             }
304             set
305             {
306                 textColorSelector.Clone(value);
307             }
308         }
309
310         /// <summary>
311         /// Add tab item by item data. The added item will be added to end of all items automatically.
312         /// </summary>
313         /// <param name="itemData">Item data which will apply to tab item view.</param>
314         /// <since_tizen> 6 </since_tizen>
315         public void AddItem(TabItemData itemData)
316         {
317             AddItemByIndex(itemData, itemList.Count);
318         }
319
320         /// <summary>
321         /// Insert tab item by item data. The inserted item will be added to the special position by index automatically.
322         /// </summary>
323         /// <param name="itemData">Item data which will apply to tab item view.</param>
324         /// <param name="index">Position index where will be inserted.</param>
325         /// <since_tizen> 6 </since_tizen>
326         public void InsertItem(TabItemData itemData, int index)
327         {
328             AddItemByIndex(itemData, index);
329         }
330
331         /// <summary>
332         /// Delete tab item by index.
333         /// </summary>
334         /// <param name="itemIndex">Position index where will be deleted.</param>
335         /// <since_tizen> 6 </since_tizen>
336         public void DeleteItem(int itemIndex)
337         {
338             if(itemList == null || itemIndex < 0 || itemIndex >= itemList.Count)
339             {
340                 return;
341             }
342
343             if (curIndex > itemIndex || (curIndex == itemIndex && itemIndex == itemList.Count - 1))
344             {
345                 curIndex--;
346             }
347
348             Remove(itemList[itemIndex]);
349             itemList[itemIndex].Dispose();
350             itemList.RemoveAt(itemIndex);
351
352             UpdateItems();
353         }
354
355         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
356         [EditorBrowsable(EditorBrowsableState.Never)]
357         public override void ApplyStyle(ViewStyle viewStyle)
358         {
359             base.ApplyStyle(viewStyle);
360
361             TabStyle tabStyle = viewStyle as TabStyle;
362
363             if (null != tabStyle)
364             {
365                 if (null == underline)
366                 {
367                     underline = new View()
368                     {
369                         PositionUsesPivotPoint = true,
370                         ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft,
371                         PivotPoint = Tizen.NUI.PivotPoint.BottomLeft,
372                     };
373                     Add(underline);
374                     CreateUnderLineAnimation();
375                 }
376
377                 underline.ApplyStyle(Style.UnderLine);
378             }
379         }
380
381         /// <summary>
382         /// Dispose Tab and all children on it.
383         /// </summary>
384         /// <param name="type">Dispose type.</param>
385         /// <since_tizen> 6 </since_tizen>
386         protected override void Dispose(DisposeTypes type)
387         {
388             if (disposed)
389             {
390                 return;
391             }
392
393             if (type == DisposeTypes.Explicit)
394             {
395                 if(underlineAni != null)
396                 {
397                     if(underlineAni.State == Animation.States.Playing)
398                     {
399                         underlineAni.Stop();
400                     }
401                     underlineAni.Dispose();
402                     underlineAni = null;
403                 }
404                 Utility.Dispose(underline);
405                 if(itemList != null)
406                 {
407                     for(int i = 0; i < itemList.Count; i++)
408                     {
409                         Remove(itemList[i]);
410                         itemList[i].Dispose();
411                         itemList[i] = null;
412                     }
413                     itemList.Clear();
414                     itemList = null;
415                 }
416             }
417
418             base.Dispose(type);
419         }
420
421         /// <summary>
422         /// Update Tab by attributes.
423         /// </summary>
424         /// <since_tizen> 6 </since_tizen>
425         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
426         [EditorBrowsable(EditorBrowsableState.Never)]
427         protected override void OnUpdate()
428         {
429             LayoutChild();
430         }
431
432         /// <summary>
433         /// Get Tab attribues.
434         /// </summary>
435         /// <since_tizen> 6 </since_tizen>
436         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
437         [EditorBrowsable(EditorBrowsableState.Never)]
438         protected override ViewStyle GetViewStyle()
439         {
440             return new TabStyle();
441         }
442
443         /// <summary>
444         /// Theme change callback when theme is changed, this callback will be trigger.
445         /// </summary>
446         /// <since_tizen> 6 </since_tizen>
447         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
448         [EditorBrowsable(EditorBrowsableState.Never)]
449         protected override void OnThemeChangedEvent(object sender, StyleManager.ThemeChangeEventArgs e)
450         {
451             TabStyle tempAttributes = StyleManager.Instance.GetViewStyle(style) as TabStyle;
452             if (tempAttributes != null)
453             {
454                 Style.CopyFrom(tempAttributes);
455             }
456         }
457
458         /// <summary>
459         /// Layout child in Tab and it can be override by user.
460         /// </summary>
461         /// <since_tizen> 6 </since_tizen>
462         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
463         [EditorBrowsable(EditorBrowsableState.Never)]
464         protected virtual void LayoutChild()
465         {
466             if (itemList == null)
467             {
468                 return;
469             }
470
471             int totalNum = itemList.Count;
472             if (totalNum == 0)
473             {
474                 return;
475             }
476
477             int preX = (int)Style.ItemPadding.Start;
478             int preW = 0;
479             int itemSpace = Style.ItemSpace;
480
481             if (LayoutDirection == ViewLayoutDirectionType.LTR)
482             {
483                 if (Style.UseTextNaturalSize == true)
484                 {
485                     for (int i = 0; i < totalNum; i++)
486                     {
487                         preW = (itemList[i].TextItem.NaturalSize2D != null ? itemList[i].TextItem.NaturalSize2D.Width : 0);
488                         itemList[i].Position2D.X = preX;
489                         itemList[i].Size2D.Width = preW;
490                         preX = itemList[i].Position2D.X + preW + itemSpace;
491                         itemList[i].Index = i;
492                     }
493                 }
494                 else
495                 {
496                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
497                     for (int i = 0; i < totalNum; i++)
498                     {
499                         itemList[i].Position2D.X = preX;
500                         itemList[i].Size2D.Width = preW;
501                         preX = itemList[i].Position2D.X + preW + itemSpace;
502                         itemList[i].Index = i;
503                     }
504                 }
505             }
506             else
507             {
508                 preX = (int)Style.ItemPadding.End;
509                 if (Style.UseTextNaturalSize == true)
510                 {
511                     int w = Size2D.Width;
512                     for (int i = 0; i < totalNum; i++)
513                     {
514                         preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0);
515                         itemList[i].Position2D.X = w - preW - preX;
516                         itemList[i].Size2D.Width = preW;
517                         preX = w - itemList[i].Position2D.X + itemSpace;
518                         itemList[i].Index = i;
519                     }
520                 }
521                 else
522                 {
523                     preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum;
524                     for (int i = totalNum - 1; i >= 0; i--)
525                     {
526                         itemList[i].Position2D.X = preX;
527                         itemList[i].Size2D.Width = preW;
528                         preX = itemList[i].Position2D.X + preW + itemSpace;
529                         itemList[i].Index = i;
530                     }
531                 }
532             }
533             UpdateUnderLinePos();
534         }
535
536         private void Initialize()
537         {
538             LayoutDirectionChanged += OnLayoutDirectionChanged;
539         }
540
541         private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
542         {
543             LayoutChild();
544         }
545
546         private void AddItemByIndex(TabItemData itemData, int index)
547         {
548             if (null == itemData) return;
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 }