This is RFC 14.
Signed-off-by: chungryeol Lim <cdark.lim@samsung.com>
Change-Id: Ie70270fbe1a1ad37f2c9d4bfd5ca0913218eca37
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using ElmSharp;
+using XForms = Xamarin.Forms;
+using XFPlatformTizen = Xamarin.Forms.Platform.Tizen;
+using TForms = Xamarin.Forms.Platform.Tizen.Forms;
+
+[assembly: XForms.Dependency(typeof(Tizen.Xamarin.Forms.Extension.Renderer.ContextPopupImplementation))]
+
+namespace Tizen.Xamarin.Forms.Extension.Renderer
+{
+ internal class ContextPopupImplementation : IContextPopup, INotifyPropertyChanged, IDisposable
+ {
+ ElmSharp.ContextPopup _popup;
+ IDictionary<ContextPopupItem, ElmSharp.ContextPopupItem> _items;
+ ContextPopupOrientation _orientation = ContextPopupOrientation.Vertical;
+ bool _isAutoHidingEnabled = true;
+
+ ContextPopupDirectionPriorities _priorities =
+ new ContextPopupDirectionPriorities(ContextPopupDirection.Up, ContextPopupDirection.Left, ContextPopupDirection.Right, ContextPopupDirection.Down);
+
+ ContextPopupItem _selectedItem = null;
+ bool _isDisposed;
+
+ public ContextPopupImplementation()
+ {
+ _popup = new ElmSharp.ContextPopup(TForms.Context.MainWindow);
+
+ _popup.KeyUp += (s, e) =>
+ {
+ if (e.KeyName == EvasKeyEventArgs.PlatformBackButtonName)
+ _popup.Dismiss();
+ };
+
+ _popup.Dismissed += (s, e) =>
+ {
+ Dismissed?.Invoke(this, EventArgs.Empty);
+ KeyUngrab();
+ };
+
+ _items = new Dictionary<ContextPopupItem, ElmSharp.ContextPopupItem>();
+ }
+
+ ~ContextPopupImplementation()
+ {
+ Dispose(false);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public event EventHandler SelectedIndexChanged;
+
+ public event EventHandler Dismissed;
+
+ public ContextPopupOrientation Orientation
+ {
+ get
+ {
+ return _orientation;
+ }
+ set
+ {
+ _orientation = value;
+ UpdateOrientation();
+ OnPropertyChanged();
+ }
+ }
+
+ public bool IsAutoHidingEnabled
+ {
+ get
+ {
+ return _isAutoHidingEnabled;
+ }
+
+ set
+ {
+ _isAutoHidingEnabled = value;
+ UpdateIsAutoHidingEnabled();
+ OnPropertyChanged();
+ }
+ }
+
+ public ContextPopupDirectionPriorities DirectionPriorities
+ {
+ get
+ {
+ return _priorities;
+ }
+
+ set
+ {
+ _priorities = value;
+ UpdateDirectionPriorities();
+ OnPropertyChanged();
+ }
+ }
+
+ public ContextPopupItem SelectedItem
+ {
+ get
+ {
+ return _selectedItem;
+ }
+
+ set
+ {
+ _selectedItem = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public void Dismiss()
+ {
+ _popup.Dismiss();
+ }
+
+ public void AddItems(IEnumerable<ContextPopupItem> items)
+ {
+ foreach (var item in items)
+ {
+ AddItem(item);
+ }
+ }
+
+ public void RemoveItems(IEnumerable<ContextPopupItem> items)
+ {
+ foreach (var item in items)
+ {
+ var nativeItem = _items[item];
+ nativeItem.Delete();
+ _items.Remove(item);
+ }
+ }
+
+ public void ClearItems()
+ {
+ _items.Clear();
+ _popup.Clear();
+ }
+
+ public void Show(XForms.View anchor)
+ {
+ var geometry = XFPlatformTizen.Platform.GetRenderer(anchor).NativeView.Geometry;
+ _popup.Move(geometry.X, geometry.Y);
+ _popup.Show();
+ KeyGrab();
+ }
+
+ public bool TryGetContextPopupDirection(out ContextPopupDirection direction)
+ {
+ var nativeDirection = _popup.Direction;
+ if (nativeDirection != ElmSharp.ContextPopupDirection.Unknown)
+ {
+ direction = (ContextPopupDirection)nativeDirection;
+ return true;
+ }
+ else
+ {
+ direction = default(ContextPopupDirection);
+ return false;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool isDisposing)
+ {
+ if (_isDisposed)
+ return;
+
+ if (isDisposing)
+ {
+ if (_popup != null)
+ {
+ _popup.Unrealize();
+ _popup = null;
+ }
+ }
+
+ _isDisposed = true;
+ }
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ void KeyGrab()
+ {
+ _popup.KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, true);
+ }
+
+ void KeyUngrab()
+ {
+ _popup.KeyUngrab(EvasKeyEventArgs.PlatformBackButtonName);
+ }
+
+ void UpdateDirectionPriorities()
+ {
+ _popup.SetDirectionPriorty(
+ (ElmSharp.ContextPopupDirection)_priorities.First,
+ (ElmSharp.ContextPopupDirection)_priorities.Second,
+ (ElmSharp.ContextPopupDirection)_priorities.Third,
+ (ElmSharp.ContextPopupDirection)_priorities.Fourth);
+ }
+
+ void UpdateIsAutoHidingEnabled()
+ {
+ _popup.AutoHide = IsAutoHidingEnabled;
+ }
+
+ void UpdateOrientation()
+ {
+ _popup.IsHorizontal = Orientation == ContextPopupOrientation.Horizontal ? true : false;
+ }
+
+ void AddItem(ContextPopupItem item)
+ {
+ if (_items.ContainsKey(item))
+ return;
+
+ ElmSharp.ContextPopupItem nativeItem;
+ if (string.IsNullOrEmpty(item.Icon))
+ {
+ nativeItem = _popup.Append(item.Label);
+ }
+ else
+ {
+ nativeItem = AppendItemWithIcon(item);
+ }
+
+ _items.Add(item, nativeItem);
+
+ nativeItem.Selected += (s, e) =>
+ {
+ if (SelectedItem != item)
+ {
+ //When SelectedItem is changed, SelectedIndexChanged is invoked.
+ SelectedItem = item;
+ }
+ else
+ {
+ //If not a newly selected item, invoke SelectedIndexChanged.
+ SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
+ };
+ };
+ }
+
+ ElmSharp.ContextPopupItem AppendItemWithIcon(ContextPopupItem item)
+ {
+ ElmSharp.ContextPopupItem nativeItem;
+ Icon icon = new Icon(_popup);
+ icon.StandardIconName = item.Icon;
+ if (!string.IsNullOrEmpty(icon.StandardIconName))
+ {
+ nativeItem = _popup.Append(item.Label, icon);
+ }
+ else
+ {
+ //Not a standard icon
+ XFPlatformTizen.Native.Image iconImage = new XFPlatformTizen.Native.Image(_popup);
+ var task = iconImage.LoadFromImageSourceAsync(item.Icon);
+ nativeItem = _popup.Append(item.Label, iconImage);
+ }
+
+ return nativeItem;
+ }
+ }
+}
\ No newline at end of file
<Compile Include="ColorSelectorRenderer.cs" />\r
<Compile Include="MediaViewRenderer.cs" />\r
<Compile Include="DropdownListRenderer.cs" />\r
+ <Compile Include="ContextPopupImplementation.cs" />\r
<Compile Include="BackgroundRenderer.cs" />\r
<Compile Include="CalendarViewRenderer.cs" />\r
<Compile Include="Cells\DoubleLabelCellRenderer.cs" />\r
--- /dev/null
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+ public class ContextPopup : BindableObject
+ {
+ IContextPopup _contextPopup;
+ ObservableCollection<ContextPopupItem> _items;
+
+ static ContextPopupDirectionPriorities _priorities =
+ new ContextPopupDirectionPriorities(ContextPopupDirection.Up, ContextPopupDirection.Left, ContextPopupDirection.Right, ContextPopupDirection.Down);
+
+ public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(ContextPopupOrientation), typeof(ContextPopup),
+ defaultValue: ContextPopupOrientation.Vertical);
+
+ public static readonly BindableProperty IsAutoHidingEnabledProperty = BindableProperty.Create(nameof(IsAutoHidingEnabled), typeof(bool), typeof(ContextPopup), defaultValue: true);
+
+ public static readonly BindableProperty DirectionPrioritiesProperty = BindableProperty.Create(nameof(DirectionPriorities), typeof(ContextPopupDirectionPriorities),
+ typeof(ContextPopup), defaultValue: _priorities);
+
+ public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(ContextPopup), defaultValue: -1,
+ propertyChanged: OnSelectedIndexChanged);
+
+ public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(ContextPopup), null,
+ propertyChanged: OnSelectedItemChanged);
+
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(ContextPopup), default(IList),
+ propertyChanged: OnItemsSourceChanged);
+
+ public ContextPopup()
+ {
+ _contextPopup = DependencyService.Get<IContextPopup>(DependencyFetchTarget.NewInstance);
+ _contextPopup.Dismissed += (s, e) => Dismissed?.Invoke(this, EventArgs.Empty);
+ _contextPopup.SelectedIndexChanged += (s, e) => SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
+
+ _items = new ObservableCollection<ContextPopupItem>();
+ _items.CollectionChanged += ItemsCollectionChanged;
+
+ SetBinding(OrientationProperty, new Binding(nameof(Orientation), mode: BindingMode.TwoWay, source: _contextPopup));
+ SetBinding(IsAutoHidingEnabledProperty, new Binding(nameof(IsAutoHidingEnabled), mode: BindingMode.TwoWay, source: _contextPopup));
+ SetBinding(DirectionPrioritiesProperty, new Binding(nameof(DirectionPriorities), mode: BindingMode.TwoWay, source: _contextPopup));
+ SetBinding(SelectedItemProperty, new Binding(nameof(SelectedItem), mode: BindingMode.TwoWay, source: _contextPopup));
+ }
+
+ public event EventHandler Dismissed;
+
+ public event EventHandler SelectedIndexChanged;
+
+ public ContextPopupOrientation Orientation
+ {
+ get { return (ContextPopupOrientation)GetValue(OrientationProperty); }
+ set { SetValue(OrientationProperty, value); }
+ }
+
+ public bool IsAutoHidingEnabled
+ {
+ get { return (bool)GetValue(IsAutoHidingEnabledProperty); }
+ set { SetValue(IsAutoHidingEnabledProperty, value); }
+ }
+
+ public ContextPopupDirectionPriorities DirectionPriorities
+ {
+ get { return (ContextPopupDirectionPriorities)GetValue(DirectionPrioritiesProperty); }
+ set { SetValue(DirectionPrioritiesProperty, value); }
+ }
+
+ public int SelectedIndex
+ {
+ get { return (int)GetValue(SelectedIndexProperty); }
+ set { SetValue(SelectedIndexProperty, value); }
+ }
+
+ public object SelectedItem
+ {
+ get { return (ContextPopupItem)GetValue(SelectedItemProperty); }
+ set { SetValue(SelectedItemProperty, value); }
+ }
+
+ public IList ItemsSource
+ {
+ get { return (IList)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ public IList<ContextPopupItem> Items
+ {
+ get
+ {
+ return _items;
+ }
+ }
+
+ public void Show(View anchor)
+ {
+ _contextPopup.Show(anchor);
+ }
+
+ public void Dismiss()
+ {
+ _contextPopup.Dismiss();
+ }
+
+ public bool TryGetContextPopupDirection(out ContextPopupDirection direction)
+ {
+ direction = default(ContextPopupDirection);
+ return _contextPopup.TryGetContextPopupDirection(out direction);
+ }
+
+ void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ var addedItems = e.NewItems.OfType<ContextPopupItem>();
+ _contextPopup.AddItems(addedItems);
+ foreach (var item in addedItems)
+ {
+ item.PropertyChanged += ContextPopupItemPropertyChanged;
+ }
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ _contextPopup.RemoveItems(e.OldItems.OfType<ContextPopupItem>());
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ _contextPopup.ClearItems();
+ SelectedIndex = -1;
+ break;
+
+ default: //Clear and add again for anything else such as Move
+ _contextPopup.ClearItems();
+ _contextPopup.AddItems(_items);
+ SelectedIndex = -1;
+ break;
+ }
+ }
+
+ void ContextPopupItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ //Clear completely and then add all items again because for example, if initially there was
+ //no icon, an icon cannot be added later using the native APIs. However, this scenario is
+ //possible in the Tizen.Xamarin.Forms.Extension APIs.
+ //And, the native APIs do not provide any method to "InsertAt" or "Replace", so we cannot
+ //just replace the item which had a property change.
+ _contextPopup.ClearItems();
+ _contextPopup.AddItems(_items);
+ }
+
+ static void OnSelectedIndexChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var contextPopup = (ContextPopup)bindable;
+ contextPopup.UpdateSelectedItem();
+ contextPopup.SelectedIndexChanged?.Invoke(contextPopup, EventArgs.Empty);
+ }
+
+ void UpdateSelectedItem()
+ {
+ if (SelectedIndex == -1)
+ {
+ SelectedItem = null;
+ return;
+ }
+
+ if (ItemsSource != null)
+ {
+ SelectedItem = ItemsSource[SelectedIndex];
+ return;
+ }
+
+ SelectedItem = Items[SelectedIndex];
+ }
+
+ static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var contextPopup = (ContextPopup)bindable;
+ contextPopup.UpdateSelectedIndex(newValue);
+ }
+
+ void UpdateSelectedIndex(object selectedItem)
+ {
+ SelectedIndex = Items.IndexOf(selectedItem);
+ }
+
+ static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ ((ContextPopup)bindable).OnItemsSourceChanged((IList)oldValue, (IList)newValue);
+ }
+
+ void OnItemsSourceChanged(IList oldValue, IList newValue)
+ {
+ var oldObservable = oldValue as INotifyCollectionChanged;
+ if (oldObservable != null)
+ oldObservable.CollectionChanged -= ItemsCollectionChanged;
+
+ var newObservable = newValue as INotifyCollectionChanged;
+ if (newObservable != null)
+ {
+ newObservable.CollectionChanged += ItemsCollectionChanged;
+ }
+
+ if (newValue != null)
+ {
+ _items.Clear();
+ foreach (var item in newValue)
+ {
+ _items.Add(item as ContextPopupItem);
+ }
+ }
+ else
+ {
+ _items.Clear();
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+namespace Tizen.Xamarin.Forms.Extension
+{
+ public enum ContextPopupDirection
+ {
+ Down,
+ Right,
+ Left,
+ Up,
+ }
+}
\ No newline at end of file
--- /dev/null
+namespace Tizen.Xamarin.Forms.Extension
+{
+ public struct ContextPopupDirectionPriorities
+ {
+ public ContextPopupDirectionPriorities(ContextPopupDirection first, ContextPopupDirection second, ContextPopupDirection third, ContextPopupDirection fourth)
+ {
+ First = first;
+ Second = second;
+ Third = third;
+ Fourth = fourth;
+ }
+
+ public ContextPopupDirection First { get; private set; }
+ public ContextPopupDirection Second { get; private set; }
+ public ContextPopupDirection Third { get; private set; }
+ public ContextPopupDirection Fourth { get; private set; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+ public class ContextPopupItem : INotifyPropertyChanged
+ {
+ string _label;
+ FileImageSource _icon;
+
+ public ContextPopupItem(string label)
+ {
+ _label = label;
+ }
+
+ public ContextPopupItem(string label, FileImageSource icon)
+ {
+ _label = label;
+ _icon = icon;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Label
+ {
+ get
+ {
+ return _label;
+ }
+ set
+ {
+ if (value != _label)
+ {
+ _label = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public FileImageSource Icon
+ {
+ get
+ {
+ return _icon;
+ }
+ set
+ {
+ if (value != _icon)
+ {
+ _icon = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+namespace Tizen.Xamarin.Forms.Extension
+{
+ public enum ContextPopupOrientation
+ {
+ Horizontal,
+ Vertical,
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+
+namespace Tizen.Xamarin.Forms.Extension
+{
+ internal interface IContextPopup
+ {\r
+ event EventHandler SelectedIndexChanged;\r
+\r
+ event EventHandler Dismissed;\r
+\r
+ ContextPopupOrientation Orientation { get; set; }\r
+\r
+ ContextPopupItem SelectedItem { get; set; }\r
+\r
+ ContextPopupDirectionPriorities DirectionPriorities { get; set; }\r
+\r
+ bool IsAutoHidingEnabled { get; set; }
+
+ void AddItems(IEnumerable<ContextPopupItem> items);
+
+ void RemoveItems(IEnumerable<ContextPopupItem> items);
+
+ void ClearItems();
+
+ void Show(View anchor);
+
+ void Dismiss();
+
+ bool TryGetContextPopupDirection(out ContextPopupDirection direction);
+ }
+}
\ No newline at end of file
<Compile Include="ColorChangedEventArgs.cs" />\r
<Compile Include="ColorSelector.cs" />\r
<Compile Include="IMediaViewController.cs" />\r
+ <Compile Include="LongTapGestureRecognizer.cs" />\r
+ <Compile Include="LongTapUpdatedEventArgs.cs" />\r
<Compile Include="MediaView.cs" />\r
<Compile Include="Background.cs" />\r
<Compile Include="BackgroundOptions.cs" />\r
<Compile Include="Cells\MultilineCell.cs" />\r
<Compile Include="DateTimeView.cs" />\r
<Compile Include="Dialog.cs" />\r
+ <Compile Include="ContextPopupItem.cs" />\r
+ <Compile Include="ContextPopupDirectionPriorities.cs" />\r
+ <Compile Include="ContextPopupDirection.cs" />\r
+ <Compile Include="ContextPopupOrientation.cs" />\r
+ <Compile Include="ContextPopup.cs" />\r
<Compile Include="EnumerableExtensions.cs" />\r
<Compile Include="FloatingButtonItem.cs" />\r
<Compile Include="FloatingButtonMovablePosition.cs" />\r
<Compile Include="GridViewEventArgs.cs" />\r
<Compile Include="IDialog.cs" />\r
<Compile Include="ILongTapGestureController.cs" />\r
+ <Compile Include="IContextPopup.cs" />\r
<Compile Include="ItemsView.cs" />\r
<Compile Include="IToast.cs" />\r
<Compile Include="ListProxy.cs" />\r
- <Compile Include="LongTapGestureRecognizer.cs" />\r
- <Compile Include="LongTapUpdatedEventArgs.cs" />\r
<Compile Include="Properties\AssemblyInfo.cs" />\r
<Compile Include="DropdownList.cs" />\r
<Compile Include="RadioButton.cs" />\r