/* * Copyright(c) 2019 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.Collections.Generic; using Tizen.NUI.BaseComponents; using System.ComponentModel; namespace Tizen.NUI.Components { /// /// Tab is one kind of common component, it can be used as menu label. /// User can handle Tab by adding/inserting/deleting TabItem. /// /// 6 public class Tab : Control { private const int aniTime = 100; // will be defined in const file later private List itemList = new List(); private int curIndex = 0; private View underline = null; private Animation underlineAni = null; private bool isNeedAnimation = false; private Extents space; /// /// Creates a new instance of a Tab. /// /// 6 public Tab() : base() { Initialize(); } /// /// Creates a new instance of a Tab with style. /// /// Create Tab by special style defined in UX. /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public Tab(string style) : base(style) { Initialize(); } /// /// Creates a new instance of a Tab with style. /// /// Create Tab by style customized by user. /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public Tab(TabStyle style) : base(style) { Initialize(); } /// /// An event for the item changed signal which can be used to subscribe or unsubscribe the event handler provided by the user.
///
/// 6 public event EventHandler ItemChangedEvent; /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public new TabStyle Style => ViewStyle as TabStyle; /// /// Selected item's index in Tab. /// /// 6 public int SelectedItemIndex { get { return curIndex; } set { if (value < itemList.Count) { UpdateSelectedItem(itemList[value]); } } } /// /// Flag to decide if TabItem is adjusted by text's natural width. /// 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. /// /// 6 public bool UseTextNaturalSize { get { return Style.UseTextNaturalSize; } set { Style.UseTextNaturalSize = value; RelayoutRequest(); } } /// /// Gap between items. /// /// 6 public int ItemSpace { get { return Style.ItemSpace; } set { Style.ItemSpace = value; RelayoutRequest(); } } /// /// Space in Tab. Sequence as Left, Right, Top, Bottom /// /// 6 public Extents Space { get { return ItemPadding; } set { ItemPadding = value; } } /// /// Item paddings in Tab. Sequence as Left, Right, Top, Bottom /// /// 6 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public Extents ItemPadding { get { return space; } set { if(null != value) { Style.ItemPadding.CopyFrom(value); if (null == space) { space = new Extents((ushort start, ushort end, ushort top, ushort bottom) => { Style.ItemPadding.Start = start; Style.ItemPadding.End = end; Style.ItemPadding.Top = top; Style.ItemPadding.Bottom = bottom; RelayoutRequest(); }, value.Start, value.End, value.Top, value.Bottom); } else { space.CopyFrom(value); } RelayoutRequest(); } } } /// /// UnderLine view's size in Tab. /// /// 6 public Size UnderLineSize { get { return Style.UnderLine?.Size; } set { if (value != null) { //CreateUnderLineAttributes(); Style.UnderLine.Size = value; //RelayoutRequest(); } } } /// /// UnderLine view's background in Tab. /// /// 6 public Color UnderLineBackgroundColor { get { return Style.UnderLine?.BackgroundColor?.All; } set { if (value != null) { //CreateUnderLineAttributes(); if (Style.UnderLine.BackgroundColor == null) { Style.UnderLine.BackgroundColor = new ColorSelector(); } if (Style.UnderLine.BackgroundColor != null) { Style.UnderLine.BackgroundColor.All = value; } //RelayoutRequest(); } } } /// /// Text point size in Tab. /// /// 6 public float PointSize { get { return Style.Text?.PointSize?.All ?? 0; } set { //CreateTextAttributes(); if (Style.Text.PointSize == null) { Style.Text.PointSize = new FloatSelector(); } if (Style.Text.PointSize != null) { Style.Text.PointSize.All = value; } //RelayoutRequest(); } } /// /// Text font family in Tab. /// /// 6 public string FontFamily { get { return Style.Text?.FontFamily.All; } set { //CreateTextAttributes(); Style.Text.FontFamily.All = value; //RelayoutRequest(); } } /// /// Text color in Tab. /// /// 6 public Color TextColor { get { return Style.Text?.TextColor?.All; } set { //CreateTextAttributes(); if (Style.Text.TextColor == null) { Style.Text.TextColor = new ColorSelector(); } if (Style.Text.TextColor != null) { Style.Text.TextColor.All = value; } //RelayoutRequest(); } } private ColorSelector textColorSelector = new ColorSelector(); /// /// Text color selector in Tab. /// /// 6 public ColorSelector TextColorSelector { get { return textColorSelector; } set { textColorSelector.Clone(value); } } /// /// Add tab item by item data. The added item will be added to end of all items automatically. /// /// Item data which will apply to tab item view. /// 6 public void AddItem(TabItemData itemData) { AddItemByIndex(itemData, itemList.Count); } /// /// Insert tab item by item data. The inserted item will be added to the special position by index automatically. /// /// Item data which will apply to tab item view. /// Position index where will be inserted. /// 6 public void InsertItem(TabItemData itemData, int index) { AddItemByIndex(itemData, index); } /// /// Delete tab item by index. /// /// Position index where will be deleted. /// 6 public void DeleteItem(int itemIndex) { if(itemList == null || itemIndex < 0 || itemIndex >= itemList.Count) { return; } if (curIndex > itemIndex || (curIndex == itemIndex && itemIndex == itemList.Count - 1)) { curIndex--; } Remove(itemList[itemIndex]); itemList[itemIndex].Dispose(); itemList.RemoveAt(itemIndex); UpdateItems(); } /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public override void ApplyStyle(ViewStyle viewStyle) { base.ApplyStyle(viewStyle); TabStyle tabStyle = viewStyle as TabStyle; if (null != tabStyle) { if (null == underline) { underline = new View() { PositionUsesPivotPoint = true, ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft, PivotPoint = Tizen.NUI.PivotPoint.BottomLeft, }; Add(underline); CreateUnderLineAnimation(); } underline.ApplyStyle(Style.UnderLine); } } /// /// Dispose Tab and all children on it. /// /// Dispose type. /// 6 protected override void Dispose(DisposeTypes type) { if (disposed) { return; } if (type == DisposeTypes.Explicit) { if(underlineAni != null) { if(underlineAni.State == Animation.States.Playing) { underlineAni.Stop(); } underlineAni.Dispose(); underlineAni = null; } Utility.Dispose(underline); if(itemList != null) { for(int i = 0; i < itemList.Count; i++) { Remove(itemList[i]); itemList[i].Dispose(); itemList[i] = null; } itemList.Clear(); itemList = null; } } base.Dispose(type); } /// /// Update Tab by attributes. /// /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] protected override void OnUpdate() { LayoutChild(); } /// /// Get Tab attribues. /// /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] protected override ViewStyle GetViewStyle() { return new TabStyle(); } /// /// Theme change callback when theme is changed, this callback will be trigger. /// /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] protected override void OnThemeChangedEvent(object sender, StyleManager.ThemeChangeEventArgs e) { TabStyle tempAttributes = StyleManager.Instance.GetAttributes(style) as TabStyle; if (tempAttributes != null) { Style.CopyFrom(tempAttributes); } } /// /// Layout child in Tab and it can be override by user. /// /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] protected virtual void LayoutChild() { if (itemList == null) { return; } int totalNum = itemList.Count; if (totalNum == 0) { return; } int preX = (int)Style.ItemPadding.Start; int preW = 0; int itemSpace = Style.ItemSpace; if (LayoutDirection == ViewLayoutDirectionType.LTR) { if (Style.UseTextNaturalSize == true) { for (int i = 0; i < totalNum; i++) { preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0); itemList[i].Position2D.X = preX; itemList[i].Size2D.Width = preW; preX = itemList[i].Position2D.X + preW + itemSpace; itemList[i].Index = i; } } else { preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum; for (int i = 0; i < totalNum; i++) { itemList[i].Position2D.X = preX; itemList[i].Size2D.Width = preW; preX = itemList[i].Position2D.X + preW + itemSpace; itemList[i].Index = i; } } } else { preX = (int)Style.ItemPadding.End; if (Style.UseTextNaturalSize == true) { int w = Size2D.Width; for (int i = 0; i < totalNum; i++) { preW = (itemList[i].NaturalSize2D != null ? itemList[i].NaturalSize2D.Width : 0); itemList[i].Position2D.X = w - preW - preX; itemList[i].Size2D.Width = preW; preX = w - itemList[i].Position2D.X + itemSpace; itemList[i].Index = i; } } else { preW = (Size2D.Width - (int)Style.ItemPadding.Start - (int)Style.ItemPadding.End) / totalNum; for (int i = totalNum - 1; i >= 0; i--) { itemList[i].Position2D.X = preX; itemList[i].Size2D.Width = preW; preX = itemList[i].Position2D.X + preW + itemSpace; itemList[i].Index = i; } } } UpdateUnderLinePos(); } private void Initialize() { LayoutDirectionChanged += OnLayoutDirectionChanged; } private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e) { LayoutChild(); } private void AddItemByIndex(TabItemData itemData, int index) { int h = 0; int topSpace = (int)Style.ItemPadding.Top; if (Style.UnderLine != null && Style.UnderLine.Size != null) { h = (int)Style.UnderLine.Size.Height; } Tab.TabItem item = new TabItem(); item.TextItem.ApplyStyle(Style.Text); item.Text = itemData.Text; item.Size2D.Height = Size2D.Height - h - topSpace; item.Position2D.Y = topSpace; item.TouchEvent += ItemTouchEvent; Add(item); if (index >= itemList.Count) { itemList.Add(item); } else { itemList.Insert(index, item); } UpdateItems(); } private void UpdateItems() { LayoutChild(); if (itemList != null && curIndex >= 0 && curIndex < itemList.Count) { itemList[curIndex].ControlState = ControlStates.Selected; UpdateUnderLinePos(); } else { if (underline != null) { underline.Hide(); } } } private void CreateUnderLineAttributes() { if (Style.UnderLine == null) { Style.UnderLine = new ViewStyle() { PositionUsesPivotPoint = true, ParentOrigin = Tizen.NUI.ParentOrigin.BottomLeft, PivotPoint = Tizen.NUI.PivotPoint.BottomLeft, }; } } private void CreateUnderLineAnimation() { if (underlineAni == null) { underlineAni = new Animation(aniTime); } } private void UpdateUnderLinePos() { if (underline == null || Style.UnderLine == null || Style.UnderLine.Size == null || itemList == null || itemList.Count <= 0) { return; } Style.UnderLine.Size.Width = itemList[curIndex].Size2D.Width; underline.Size2D = new Size2D(itemList[curIndex].Size2D.Width, (int)Style.UnderLine.Size.Height); underline.BackgroundColor = Style.UnderLine.BackgroundColor.All; if (isNeedAnimation) { CreateUnderLineAnimation(); if (underlineAni.State == Animation.States.Playing) { underlineAni.Stop(); } underlineAni.Clear(); underlineAni.AnimateTo(underline, "PositionX", itemList[curIndex].Position2D.X); underlineAni.Play(); } else { underline.Position2D.X = itemList[curIndex].Position2D.X; isNeedAnimation = true; } underline.Show(); } private void UpdateSelectedItem(TabItem item) { if(item == null || curIndex == item.Index) { return; } ItemChangedEventArgs e = new ItemChangedEventArgs { PreviousIndex = curIndex, CurrentIndex = item.Index }; ItemChangedEvent?.Invoke(this, e); itemList[curIndex].ControlState = ControlStates.Normal; curIndex = item.Index; itemList[curIndex].ControlState = ControlStates.Selected; UpdateUnderLinePos(); } private bool ItemTouchEvent(object source, TouchEventArgs e) { TabItem item = source as TabItem; if(item == null) { return false; } PointStateType state = e.Touch.GetState(0); if (state == PointStateType.Up) { UpdateSelectedItem(item); } return true; } internal class TabItem : View { public TabItem() : base() { TextItem = new TextLabel() { ParentOrigin = Tizen.NUI.ParentOrigin.Center, PivotPoint = Tizen.NUI.PivotPoint.Center, PositionUsesPivotPoint = true, WidthResizePolicy = ResizePolicyType.FillToParent, HeightResizePolicy = ResizePolicyType.FillToParent, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; Add(TextItem); } internal int Index { get; set; } public string Text { get { return TextItem.Text; } set { TextItem.Text = value; } } internal TextLabel TextItem { get; set; } } /// /// TabItemData is a class to record all data which will be applied to Tab item. /// /// 6 public class TabItemData { /// /// Text string in tab item view. /// /// 6 public string Text { get; set; } } /// /// ItemChangedEventArgs is a class to record item change event arguments which will sent to user. /// /// 6 public class ItemChangedEventArgs : EventArgs { /// Previous selected index of Tab /// 6 public int PreviousIndex; /// Current selected index of Tab /// 6 public int CurrentIndex; } } }