[ContextPopup] Update Document
[platform/core/csapi/xamarin-forms-extension.git] / Tizen.Xamarin.Forms.Extension / ContextPopup.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.Collections.Specialized;
6 using System.Linq;
7 using Xamarin.Forms;
8
9 namespace Tizen.Xamarin.Forms.Extension
10 {
11     /// <summary>
12     /// The ContextPopup class allows a contextual popup to be anchored at a View.
13     /// </summary>
14     /// <example>
15     /// <code>
16     /// ContextPopup popup = new ContextPopup();
17     /// popup.Items.Add(new ContextPopupItem("Text only item"));
18     /// popup.Items.Add(new ContextPopupItem("Home icon", "home"));
19     /// popup.SelectedIndexChanged += (s, e) =>
20     /// {
21     ///     var ctxPopup = s as ContextPopup;
22     ///     Debug.WriteLine("Item with index {0} selected", ctxPopup.SelectedIndex);
23     ///     Debug.WriteLine("It has label: " + (ctxPopup.SelectedItem as ContextPopupItem).Label);
24     /// };
25     ///
26     /// Button btn = new Button
27     /// {
28     ///     Text = "Toggle popup"
29     /// };
30     /// btn.Clicked += (s, e) =>
31     /// {
32     ///     popup.Show(s as Button);
33     ///     popupShowing = true;
34     /// };
35     /// </code>
36     /// </example>
37     public class ContextPopup : BindableObject
38     {
39         IContextPopup _contextPopup;
40         ObservableCollection<ContextPopupItem> _items;
41
42         static ContextPopupDirectionPriorities _priorities =
43             new ContextPopupDirectionPriorities(ContextPopupDirection.Up, ContextPopupDirection.Left, ContextPopupDirection.Right, ContextPopupDirection.Down);
44
45         public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(ContextPopupOrientation), typeof(ContextPopup),
46             defaultValue: ContextPopupOrientation.Vertical);
47
48         public static readonly BindableProperty IsAutoHidingEnabledProperty = BindableProperty.Create(nameof(IsAutoHidingEnabled), typeof(bool), typeof(ContextPopup), defaultValue: true);
49
50         public static readonly BindableProperty DirectionPrioritiesProperty = BindableProperty.Create(nameof(DirectionPriorities), typeof(ContextPopupDirectionPriorities),
51             typeof(ContextPopup), defaultValue: _priorities);
52
53         public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(ContextPopup), defaultValue: -1,
54             propertyChanged: OnSelectedIndexChanged);
55
56         public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(ContextPopup), null,
57             propertyChanged: OnSelectedItemChanged);
58
59         public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(ContextPopup), default(IList),
60             propertyChanged: OnItemsSourceChanged);
61
62         public ContextPopup()
63         {
64             _contextPopup = DependencyService.Get<IContextPopup>(DependencyFetchTarget.NewInstance);
65             _contextPopup.Dismissed += (s, e) => Dismissed?.Invoke(this, EventArgs.Empty);
66             _contextPopup.SelectedIndexChanged += (s, e) => SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
67
68             _items = new ObservableCollection<ContextPopupItem>();
69             _items.CollectionChanged += ItemsCollectionChanged;
70
71             SetBinding(OrientationProperty, new Binding(nameof(Orientation), mode: BindingMode.TwoWay, source: _contextPopup));
72             SetBinding(IsAutoHidingEnabledProperty, new Binding(nameof(IsAutoHidingEnabled), mode: BindingMode.TwoWay, source: _contextPopup));
73             SetBinding(DirectionPrioritiesProperty, new Binding(nameof(DirectionPriorities), mode: BindingMode.TwoWay, source: _contextPopup));
74             SetBinding(SelectedItemProperty, new Binding(nameof(SelectedItem), mode: BindingMode.TwoWay, source: _contextPopup));
75         }
76
77         /// <summary>
78         /// Occurs when the ContextPopup is dismissed.
79         /// </summary>
80         public event EventHandler Dismissed;
81
82         /// <summary>
83         /// Occurs when an item is selected.
84         /// </summary>
85         public event EventHandler SelectedIndexChanged;
86
87         /// <summary>
88         /// Gets or sets the orientation of the ContextPopup.
89         /// The default value is ContextPopupOrientation.Vertical.
90         /// </summary>
91         public ContextPopupOrientation Orientation
92         {
93             get { return (ContextPopupOrientation)GetValue(OrientationProperty); }
94             set { SetValue(OrientationProperty, value); }
95         }
96
97         public bool IsAutoHidingEnabled
98         {
99             get { return (bool)GetValue(IsAutoHidingEnabledProperty); }
100             set { SetValue(IsAutoHidingEnabledProperty, value); }
101         }
102
103         /// <summary>
104         /// Gets or sets the direction priorities for the ContextPopup.
105         /// </summary>
106         public ContextPopupDirectionPriorities DirectionPriorities
107         {
108             get { return (ContextPopupDirectionPriorities)GetValue(DirectionPrioritiesProperty); }
109             set { SetValue(DirectionPrioritiesProperty, value); }
110         }
111
112         /// <summary>
113         /// Gets or sets the index of the selected item of the ContextPopup.
114         /// It is -1 when no item is selected.
115         /// </summary>
116         public int SelectedIndex
117         {
118             get { return (int)GetValue(SelectedIndexProperty); }
119             set { SetValue(SelectedIndexProperty, value); }
120         }
121
122         /// <summary>
123         /// Gets or sets the selected item of the ContextPopup.
124         /// </summary>
125         public object SelectedItem
126         {
127             get { return (ContextPopupItem)GetValue(SelectedItemProperty); }
128             set { SetValue(SelectedItemProperty, value); }
129         }
130
131         /// <summary>
132         /// Gets or sets the source list of items for the ContextPopup.
133         /// </summary>
134         public IList ItemsSource
135         {
136             get { return (IList)GetValue(ItemsSourceProperty); }
137             set { SetValue(ItemsSourceProperty, value); }
138         }
139
140         /// <summary>
141         /// Gets the list of items in the ContextPopup.
142         /// </summary>
143         public IList<ContextPopupItem> Items
144         {
145             get
146             {
147                 return _items;
148             }
149         }
150
151         /// <summary>
152         /// Shows the ContextPopup.
153         /// </summary>
154         /// <param name="anchor">The View to which the popup should be anchored.</param>
155         public void Show(View anchor)
156         {
157             _contextPopup.Show(anchor);
158         }
159
160         /// <summary>
161         /// Dismisses the ContextPopup.
162         /// </summary>
163         public void Dismiss()
164         {
165             _contextPopup.Dismiss();
166         }
167
168         /// <summary>
169         /// Gets the direction of the ContextPopup if it is shown.
170         /// This method returns false if it is not shown and the output argument is a default value.
171         /// </summary>
172         /// <param name="direction">The direction of the ContextPopup.</param>
173         /// <returns>true if the ContextPopup is shown, false otherwise.</returns>
174         public bool TryGetContextPopupDirection(out ContextPopupDirection direction)
175         {
176             direction = default(ContextPopupDirection);
177             return _contextPopup.TryGetContextPopupDirection(out direction);
178         }
179
180         void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
181         {
182             switch (e.Action)
183             {
184                 case NotifyCollectionChangedAction.Add:
185                     var addedItems = e.NewItems.OfType<ContextPopupItem>();
186                     _contextPopup.AddItems(addedItems);
187                     foreach (var item in addedItems)
188                     {
189                         item.PropertyChanged += ContextPopupItemPropertyChanged;
190                     }
191                     break;
192
193                 case NotifyCollectionChangedAction.Remove:
194                     _contextPopup.RemoveItems(e.OldItems.OfType<ContextPopupItem>());
195                     break;
196
197                 case NotifyCollectionChangedAction.Reset:
198                     _contextPopup.ClearItems();
199                     SelectedIndex = -1;
200                     break;
201
202                 default: //Clear and add again for anything else such as Move
203                     _contextPopup.ClearItems();
204                     _contextPopup.AddItems(_items);
205                     SelectedIndex = -1;
206                     break;
207             }
208         }
209
210         void ContextPopupItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
211         {
212             //Clear completely and then add all items again because for example, if initially there was
213             //no icon, an icon cannot be added later using the native APIs. However, this scenario is
214             //possible in the Tizen.Xamarin.Forms.Extension APIs.
215             //And, the native APIs do not provide any method to "InsertAt" or "Replace", so we cannot
216             //just replace the item which had a property change.
217             _contextPopup.ClearItems();
218             _contextPopup.AddItems(_items);
219         }
220
221         static void OnSelectedIndexChanged(BindableObject bindable, object oldValue, object newValue)
222         {
223             var contextPopup = (ContextPopup)bindable;
224             contextPopup.UpdateSelectedItem();
225             contextPopup.SelectedIndexChanged?.Invoke(contextPopup, EventArgs.Empty);
226         }
227
228         void UpdateSelectedItem()
229         {
230             if (SelectedIndex == -1)
231             {
232                 SelectedItem = null;
233                 return;
234             }
235
236             if (ItemsSource != null)
237             {
238                 SelectedItem = ItemsSource[SelectedIndex];
239                 return;
240             }
241
242             SelectedItem = Items[SelectedIndex];
243         }
244
245         static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
246         {
247             var contextPopup = (ContextPopup)bindable;
248             contextPopup.UpdateSelectedIndex(newValue);
249         }
250
251         void UpdateSelectedIndex(object selectedItem)
252         {
253             SelectedIndex = Items.IndexOf(selectedItem);
254         }
255
256         static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
257         {
258             ((ContextPopup)bindable).OnItemsSourceChanged((IList)oldValue, (IList)newValue);
259         }
260
261         void OnItemsSourceChanged(IList oldValue, IList newValue)
262         {
263             var oldObservable = oldValue as INotifyCollectionChanged;
264             if (oldObservable != null)
265                 oldObservable.CollectionChanged -= ItemsCollectionChanged;
266
267             var newObservable = newValue as INotifyCollectionChanged;
268             if (newObservable != null)
269             {
270                 newObservable.CollectionChanged += ItemsCollectionChanged;
271             }
272
273             if (newValue != null)
274             {
275                 _items.Clear();
276                 foreach (var item in newValue)
277                 {
278                     _items.Add(item as ContextPopupItem);
279                 }
280             }
281             else
282             {
283                 _items.Clear();
284             }
285         }
286     }
287 }