Removing Tizen.Xamarin.Forms.Extensions
[profile/tv/apps/dotnet/mediahub.git] / TVMediaHub / TVMediaHub.Tizen / Extensions / 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     /// {
18     ///     DirectionPriorities = new ContextPopupDirectionPriorities(ContextPopupDirection.Down, ContextPopupDirection.Right, ContextPopupDirection.Left, ContextPopupDirection.Up),
19     /// };
20     /// popup.Items.Add(new ContextPopupItem("Text only item"));
21     /// popup.Items.Add(new ContextPopupItem("Home icon", "home"));
22     /// popup.Items.Add(new ContextPopupItem("Chat", StandardIconResource.MenuChat.Name));
23     /// popup.SelectedIndexChanged += (s, e) =>
24     /// {
25     ///     var ctxPopup = s as ContextPopup;
26     ///     Debug.WriteLine("Item with index {0} selected", ctxPopup.SelectedIndex);
27     ///     Debug.WriteLine("It has label: " + (ctxPopup.SelectedItem as ContextPopupItem).Label);
28     /// };
29     ///
30     /// Button btn = new Button
31     /// {
32     ///     Text = "Toggle popup"
33     /// };
34     /// btn.Clicked += (s, e) =>
35     /// {
36     ///     popup.Show(s as Button);
37     /// };
38     /// </code>
39     /// </example>
40     public class ContextPopup : BindableObject
41     {
42         IContextPopup _contextPopup;
43
44         ObservableCollection<ContextPopupItem> _items;
45
46         static ContextPopupDirectionPriorities _priorities =
47             new ContextPopupDirectionPriorities(ContextPopupDirection.Up, ContextPopupDirection.Left, ContextPopupDirection.Right, ContextPopupDirection.Down);
48
49         [Obsolete("OrientationProperty is obsolete as of version 2.3.5-r256-001. The orientation is always vertical.")]
50         public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(ContextPopupOrientation), typeof(ContextPopup),
51             defaultValue: ContextPopupOrientation.Vertical);
52
53         /// <summary>
54         /// BindableProperty. Identifies the IsAutoHidingEnabled bindable property.
55         /// </summary>
56         public static readonly BindableProperty IsAutoHidingEnabledProperty = BindableProperty.Create(nameof(IsAutoHidingEnabled), typeof(bool), typeof(ContextPopup), defaultValue: true);
57
58         /// <summary>
59         /// BindableProperty. Identifies the DirectionPriorities bindable property.
60         /// </summary>
61         public static readonly BindableProperty DirectionPrioritiesProperty = BindableProperty.Create(nameof(DirectionPriorities), typeof(ContextPopupDirectionPriorities),
62             typeof(ContextPopup), defaultValue: _priorities);
63
64         /// <summary>
65         /// BindableProperty. Identifies the SelectedIndex bindable property.
66         /// </summary>
67         public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(ContextPopup), defaultValue: -1,
68             propertyChanged: OnSelectedIndexChanged, coerceValue: CoerceSelectedIndex);
69
70         /// <summary>
71         /// BindableProperty. Identifies the SelectedItem bindable property.
72         /// </summary>
73         public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(ContextPopup), null,
74             propertyChanged: OnSelectedItemChanged);
75
76         [Obsolete("ItemsSourceProperty is obsolete as of version 2.3.5-r256-001. Please use Items instead.")]
77         public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(ContextPopup), default(IList));
78
79         /// <summary>
80         /// The constructor, which creates a new ContextPopup instance.
81         /// </summary>
82         public ContextPopup()
83         {
84             _contextPopup = DependencyService.Get<IContextPopup>(DependencyFetchTarget.NewInstance);
85
86             _contextPopup.Dismissed += (s, e) => Dismissed?.Invoke(this, EventArgs.Empty);
87             _contextPopup.ItemSelected += (s, e) => ItemSelected?.Invoke(this, EventArgs.Empty);
88
89             _items = new ObservableCollection<ContextPopupItem>();
90             _items.CollectionChanged += ItemsCollectionChanged;
91
92             SetBinding(IsAutoHidingEnabledProperty, new Binding(nameof(IsAutoHidingEnabled), mode: BindingMode.TwoWay, source: _contextPopup));
93             SetBinding(DirectionPrioritiesProperty, new Binding(nameof(DirectionPriorities), mode: BindingMode.TwoWay, source: _contextPopup));
94             SetBinding(SelectedItemProperty, new Binding(nameof(SelectedItem), mode: BindingMode.TwoWay, source: _contextPopup));
95         }
96
97         /// <summary>
98         /// Occurs when the ContextPopup is dismissed.
99         /// </summary>
100         public event EventHandler Dismissed;
101
102         /// <summary>
103         /// Occurs when the index of the selected ContextPopupItem changes.
104         /// </summary>
105         public event EventHandler SelectedIndexChanged;
106
107         /// <summary>
108         /// Occurs when a ContextPopupItem is selected.
109         /// </summary>
110         public event EventHandler ItemSelected;
111
112         [Obsolete("Orientation is obsolete as of version 2.3.5-r256-001. The orientation is always vertical.")]
113         public ContextPopupOrientation Orientation
114         {
115             get { return (ContextPopupOrientation)GetValue(OrientationProperty); }
116             set { SetValue(OrientationProperty, value); }
117         }
118
119         /// <summary>
120         /// Gets or sets whether ContextPopup should be hidden automatically or not when parent of ContextPopup is resized.
121         /// </summary>
122         /// <remarks>
123         /// Setting IsAutoHidingEnabled to false will not be dismissed automatically whenever such as mouse clicked its background area, language i s changed, and its parent geometry is updated(changed).
124         /// </remarks>
125         public bool IsAutoHidingEnabled
126         {
127             get { return (bool)GetValue(IsAutoHidingEnabledProperty); }
128             set { SetValue(IsAutoHidingEnabledProperty, value); }
129         }
130
131         /// <summary>
132         /// Gets or sets the direction priorities for the ContextPopup.<br>
133         /// The position of the ContextPopup depends on the available space.
134         /// Therefore, DirectionPriorities are used to specify desired first, second, third, and fourth priorities for positioning the ContextPopup.
135         /// </summary>
136         public ContextPopupDirectionPriorities DirectionPriorities
137         {
138             get { return (ContextPopupDirectionPriorities)GetValue(DirectionPrioritiesProperty); }
139             set { SetValue(DirectionPrioritiesProperty, value); }
140         }
141
142         /// <summary>
143         /// Gets or sets the index of the selected item of the ContextPopup.
144         /// It is -1 when no item is selected.
145         /// </summary>
146         public int SelectedIndex
147         {
148             get { return (int)GetValue(SelectedIndexProperty); }
149             set { SetValue(SelectedIndexProperty, value); }
150         }
151
152         /// <summary>
153         /// Gets or sets the selected item of the ContextPopup.
154         /// </summary>
155         public ContextPopupItem SelectedItem
156         {
157             get { return (ContextPopupItem)GetValue(SelectedItemProperty); }
158             set { SetValue(SelectedItemProperty, value); }
159         }
160
161         [Obsolete("ItemsSource is obsolete as of version 2.3.5-r256-001. Please use Items instead.")]
162         public IList ItemsSource
163         {
164             get { return (IList)GetValue(ItemsSourceProperty); }
165             set { SetValue(ItemsSourceProperty, value); }
166         }
167
168         /// <summary>
169         /// Gets the list of items in the ContextPopup.
170         /// </summary>
171         public IList<ContextPopupItem> Items
172         {
173             get
174             {
175                 return _items;
176             }
177         }
178
179         /// <summary>
180         /// Shows the ContextPopup. The ContextPopup is positioned at the horizontal and the vertical position of a specific anchor.
181         /// </summary>
182         /// <param name="anchor">The view to which the popup should be anchored.</param>
183         public void Show(View anchor)
184         {
185             Show(anchor, 0, 0);
186         }
187
188         /// <summary>
189         /// Shows the ContextPopup. The ContextPopup is positioned at the horizontal and the vertical position of a specific anchor with offsets.
190         /// </summary>
191         /// <param name="anchor">The view to which the popup should be anchored.</param>
192         /// <param name="xOffset">The horizontal offset from the anchor.</param>
193         /// <param name="yOffset">The vertical offset from the anchor.</param>
194         public void Show(View anchor, int xOffset, int yOffset)
195         {
196             _contextPopup.Show(anchor, xOffset, yOffset);
197         }
198
199         /// <summary>
200         /// Shows the ContextPopup. The ContextPopup is positioned at the horizontal and the vertical position of a specific anchor with offsets.
201         /// </summary>
202         /// <param name="anchor">The view to which the popup should be anchored.</param>
203         /// <param name="xOffset">The horizontal offset from the anchor.</param>
204         /// <param name="yOffset">The vertical offset from the anchor.</param>
205         public void Show(View anchor, double xOffset, double yOffset)
206         {
207             Show(anchor, (int)xOffset, (int)yOffset);
208         }
209
210         /// <summary>
211         /// Dismisses the ContextPopup.
212         /// </summary>
213         public void Dismiss()
214         {
215             _contextPopup.Dismiss();
216         }
217
218         /// <summary>
219         /// Gets the direction of the ContextPopup if it is shown.
220         /// This method returns false if it is not shown and the output argument is a default value.
221         /// </summary>
222         /// <param name="direction">The direction of the ContextPopup.</param>
223         /// <returns>true if the ContextPopup is shown, false otherwise.</returns>
224         public bool TryGetContextPopupDirection(out ContextPopupDirection direction)
225         {
226             direction = default(ContextPopupDirection);
227             return _contextPopup.TryGetContextPopupDirection(out direction);
228         }
229
230         void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
231         {
232             switch (e.Action)
233             {
234                 case NotifyCollectionChangedAction.Add:
235                     AddItems(e);
236                     break;
237
238                 case NotifyCollectionChangedAction.Remove:
239                     RemoveItems(e);
240                     break;
241
242                 default: // Move, replace, reset
243                     ResetItems();
244                     break;
245             }
246
247             SelectedIndex = SelectedIndex.Clamp(-1, Items.Count - 1);
248             UpdateSelectedItem();
249         }
250
251         void ResetItems()
252         {
253             _contextPopup.ClearItems();
254         }
255
256         void RemoveItems(NotifyCollectionChangedEventArgs e)
257         {
258             _contextPopup.RemoveItems(e.OldItems.OfType<ContextPopupItem>());
259         }
260
261         void AddItems(NotifyCollectionChangedEventArgs e)
262         {
263             _contextPopup.AddItems(e.NewItems.OfType<ContextPopupItem>());
264         }
265
266         static void OnSelectedIndexChanged(BindableObject bindable, object oldValue, object newValue)
267         {
268             var contextPopup = (ContextPopup)bindable;
269             contextPopup.UpdateSelectedItem();
270             contextPopup.SelectedIndexChanged?.Invoke(contextPopup, EventArgs.Empty);
271         }
272
273         static object CoerceSelectedIndex(BindableObject bindable, object value)
274         {
275             var contextPopup = (ContextPopup)bindable;
276             return contextPopup.Items == null ? -1 : ((int)value).Clamp(-1, contextPopup.Items.Count - 1);
277         }
278
279         void UpdateSelectedItem()
280         {
281             if (SelectedIndex == -1)
282             {
283                 SelectedItem = null;
284                 return;
285             }
286
287             SelectedItem = Items[SelectedIndex];
288         }
289
290         static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
291         {
292             var contextPopup = (ContextPopup)bindable;
293             contextPopup.UpdateSelectedIndex(newValue);
294         }
295
296         void UpdateSelectedIndex(object selectedItem)
297         {
298             SelectedIndex = Items.IndexOf(selectedItem as ContextPopupItem);
299         }
300     }
301
302     internal static class NumericExtensions
303     {
304         public static int Clamp(this int self, int min, int max)
305         {
306             return Math.Min(max, Math.Max(self, min));
307         }
308     }
309 }