Skip to content

Commit 86fc7ac

Browse files
committed
Ported Popup Control from xamarin/Xamarin.Forms#9616 to Xamarin Community Toolkit
1 parent 32d27a7 commit 86fc7ac

14 files changed

+1309
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Reflection;
2+
using Windows.UI.Xaml;
3+
using Xamarin.Forms;
4+
5+
namespace Xamarin.CommunityToolkit.Extensions
6+
{
7+
static class PageExtensions
8+
{
9+
internal static FrameworkElement ToFrameworkElement(this VisualElement visualElement)
10+
{
11+
var toFrameworkElementMethodInfo = typeof(Xamarin.Forms.Platform.UWP.PageExtensions).GetMethod("ToFrameworkElement", BindingFlags.Static | BindingFlags.NonPublic);
12+
return (FrameworkElement)toFrameworkElementMethodInfo?.Invoke(null, new[] { visualElement });
13+
}
14+
}
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using Xamarin.Forms;
3+
using Xamarin.Forms.Platform.UWP;
4+
5+
namespace Xamarin.CommunityToolkit.Extensions
6+
{
7+
public static class VisualElementExtensions
8+
{
9+
// This extension method is ported from Xamarin.Forms and should remain in sync
10+
internal static void Cleanup(this VisualElement self)
11+
{
12+
if (self == null)
13+
throw new ArgumentNullException("self");
14+
15+
var renderer = Platform.GetRenderer(self);
16+
17+
foreach (var element in self.Descendants())
18+
{
19+
var visual = element as VisualElement;
20+
if (visual == null)
21+
continue;
22+
23+
var childRenderer = Platform.GetRenderer(visual);
24+
if (childRenderer != null)
25+
{
26+
childRenderer.Dispose();
27+
Platform.SetRenderer(visual, null);
28+
}
29+
}
30+
31+
if (renderer != null)
32+
{
33+
renderer.Dispose();
34+
Platform.SetRenderer(self, null);
35+
}
36+
}
37+
}
38+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using UIKit;
2+
3+
namespace Xamarin.CommunityToolkit.Helpers
4+
{
5+
public static class FormsToolkit
6+
{
7+
static bool? isiOS9OrNewer;
8+
9+
public static bool IsiOS9OrNewer
10+
{
11+
get
12+
{
13+
if (!isiOS9OrNewer.HasValue)
14+
isiOS9OrNewer = UIDevice.CurrentDevice.CheckSystemVersion(9, 0);
15+
16+
return isiOS9OrNewer.Value;
17+
}
18+
}
19+
}
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Reflection;
2+
using Windows.UI.Xaml;
3+
using Xamarin.Forms.Platform.UWP;
4+
5+
namespace Xamarin.CommunityToolkit.UI.Views
6+
{
7+
internal static class PlatformToolkit
8+
{
9+
internal static Xamarin.Forms.Platform.UWP.Platform Current
10+
{
11+
get
12+
{
13+
var frame = Window.Current?.Content as Windows.UI.Xaml.Controls.Frame;
14+
var wbp = frame?.Content as WindowsBasePage;
15+
if (wbp == null)
16+
return null;
17+
18+
var platformPropertyInfo = wbp.GetType().GetProperty("Platform", BindingFlags.Static | BindingFlags.NonPublic);
19+
return (Xamarin.Forms.Platform.UWP.Platform)platformPropertyInfo?.GetValue(wbp);
20+
}
21+
}
22+
}
23+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using Xamarin.Forms;
3+
4+
namespace Xamarin.CommunityToolkit.UI.Views
5+
{
6+
public abstract class BasePopup : View
7+
{
8+
// The BasePopup should inherit from VisualElement instead of View.
9+
// This was agreed upon during the original PR for adding this
10+
// control to Xamarin.Forms. Currently it is impossible to inherit
11+
// from VisualElement as th constructor has an access modifier of
12+
// internal. If that is changed to protected we will be able to
13+
// change this to Visual Element.
14+
15+
16+
public BasePopup()
17+
{
18+
Color = Color.White;
19+
BorderColor = default(Color);
20+
VerticalOptions = LayoutOptions.CenterAndExpand;
21+
HorizontalOptions = LayoutOptions.CenterAndExpand;
22+
}
23+
24+
public static BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(BasePopup));
25+
public static BindableProperty SizeProperty = BindableProperty.Create(nameof(Size), typeof(Size), typeof(BasePopup));
26+
27+
// NOTE - AH 9/12/2020
28+
// These properties are required if we update the BasePopup to inherit
29+
// from VisualElement. If we decide to use View as the parent class
30+
// these can be safely removed. Leaving them commented out for now so
31+
// it can be reviewed.
32+
// public static BindableProperty VerticalOptionsProperty = BindableProperty.Create(nameof(VerticalOptions), typeof(LayoutOptions), typeof(BasePopup), LayoutOptions.CenterAndExpand);
33+
// public static BindableProperty HorizontalOptionsProperty = BindableProperty.Create(nameof(HorizontalOptions), typeof(LayoutOptions), typeof(BasePopup), LayoutOptions.CenterAndExpand);
34+
35+
/// <summary>
36+
/// Gets or sets the <see cref="View"/> to render in the Popup.
37+
/// </summary>
38+
/// <remarks>
39+
/// The View can be or type: <see cref="View"/>, <see cref="ContentPage"/> or <see cref="NavigationPage"/>
40+
/// </remarks>
41+
public virtual View View { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets the <see cref="Color"/> of the Popup.
45+
/// </summary>
46+
/// <remarks>
47+
/// This color sets the native background color of the <see cref="Popup"/>, which is
48+
/// independent of any background color configured in the actual View.
49+
/// </remarks>
50+
public Color Color
51+
{
52+
get => (Color)GetValue(ColorProperty);
53+
set => SetValue(ColorProperty, value);
54+
}
55+
56+
// NOTE - AH 9/12/2020
57+
// This property is required if we update the BasePopup to inherit
58+
// from VisualElement. If we decide to use View as the parent class
59+
// these can be safely removed. Leaving them commented out for now so
60+
// it can be reviewed.
61+
// /// <summary>
62+
// /// Gets or sets the <see cref="LayoutOptions"/> for positioning the <see cref="Popup"/> vertically on the screen.
63+
// /// </summary>
64+
// public LayoutOptions VerticalOptions
65+
// {
66+
// get => (LayoutOptions)GetValue(VerticalOptionsProperty);
67+
// set => SetValue(VerticalOptionsProperty, value);
68+
// }
69+
70+
// NOTE - AH 9/12/2020
71+
// This property is required if we update the BasePopup to inherit
72+
// from VisualElement. If we decide to use View as the parent class
73+
// these can be safely removed. Leaving them commented out for now so
74+
// it can be reviewed.
75+
// /// <summary>
76+
// /// Gets or sets the <see cref="LayoutOptions"/> for positioning the <see cref="Popup"/> horizontally on the screen.
77+
// /// </summary>
78+
// public LayoutOptions HorizontalOptions
79+
// {
80+
// get => (LayoutOptions)GetValue(HorizontalOptionsProperty);
81+
// set => SetValue(HorizontalOptionsProperty, value);
82+
// }
83+
84+
/// <summary>
85+
/// Gets or sets the <see cref="Color"/> of the Popup Border.
86+
/// </summary>
87+
/// <remarks>
88+
/// This color sets the native border color of the <see cref="Popup"/>, which is
89+
/// independent of any border color configured in the actual view.
90+
/// </remarks>
91+
public Color BorderColor { get; set; } // UWP ONLY - wasn't originally in spec
92+
93+
/// <summary>
94+
/// Gets or sets the <see cref="View"/> anchor.
95+
/// </summary>
96+
/// <remarks>
97+
/// The Anchor is where the Popup will render closest to. When an Anchor is configured
98+
/// the popup will appear centered over that control or as close as possible.
99+
/// </remarks>
100+
public View Anchor { get; set; }
101+
102+
/// <summary>
103+
/// Gets or sets the <see cref="Size"/> of the Popup Display.
104+
/// </summary>
105+
/// <remarks>
106+
/// The Popup will always try to constrain the actual size of the <see cref="Popup" />
107+
/// to the <see cref="Popup" /> of the View unless a <see cref="Size"/> is specified.
108+
/// If the <see cref="Popup" /> contiains <see cref="LayoutOptions"/> a <see cref="Size"/>
109+
/// will be required. This will allow the View to have a concept of <see cref="Size"/>
110+
/// that varies from the actual <see cref="Size"/> of the <see cref="Popup" />
111+
/// </remarks>
112+
public Size Size
113+
{
114+
get => (Size)GetValue(SizeProperty);
115+
set => SetValue(SizeProperty, value);
116+
}
117+
118+
public bool IsLightDismissEnabled { get; set; }
119+
120+
public event EventHandler<PopupDismissedEventArgs> Dismissed;
121+
122+
protected virtual void OnDismissed(object result)
123+
{
124+
Dismissed?.Invoke(this, new PopupDismissedEventArgs { Result = result });
125+
}
126+
127+
public virtual void LightDismiss()
128+
{
129+
// empty default implementation
130+
}
131+
132+
protected override void OnBindingContextChanged()
133+
{
134+
base.OnBindingContextChanged();
135+
136+
if (View != null)
137+
{
138+
SetInheritedBindingContext(View, BindingContext);
139+
}
140+
}
141+
}
142+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Xamarin.Forms;
2+
3+
namespace Xamarin.CommunityToolkit.UI.Views
4+
{
5+
public interface IPopup
6+
{
7+
View View { get; }
8+
}
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace Xamarin.CommunityToolkit.UI.Views
4+
{
5+
/// <summary>
6+
/// Specifies a View to be used as popup content for popups which return a result
7+
/// </summary>
8+
/// <typeparam name="T">The type of result returned by the popup</typeparam>
9+
public interface IPopupView<out T> : IPopup
10+
{
11+
void SetDismissDelegate(Action<T> dismissDelegate);
12+
}
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Xamarin.Forms;
2+
3+
namespace Xamarin.CommunityToolkit.UI.Views
4+
{
5+
public class Popup : Popup<object>
6+
{
7+
public Popup() { }
8+
9+
public Popup(View view)
10+
{
11+
View = view;
12+
}
13+
}
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Xamarin.CommunityToolkit.UI.Views
4+
{
5+
public class PopupDismissedEventArgs : EventArgs
6+
{
7+
public object Result { get; set; }
8+
}
9+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Threading.Tasks;
2+
using Xamarin.Forms;
3+
4+
namespace Xamarin.CommunityToolkit.UI.Views
5+
{
6+
public abstract class Popup<T> : BasePopup
7+
{
8+
TaskCompletionSource<T> _tcs;
9+
View _view;
10+
11+
protected Popup()
12+
{
13+
_tcs = new TaskCompletionSource<T>();
14+
}
15+
16+
public override View View
17+
{
18+
get => _view;
19+
set
20+
{
21+
if (_view == value)
22+
return;
23+
24+
if (value is IPopupView<T> popupView)
25+
{
26+
popupView.SetDismissDelegate(Dismiss);
27+
}
28+
29+
_view = value;
30+
}
31+
}
32+
33+
public void Reset()
34+
{
35+
_tcs = new TaskCompletionSource<T>();
36+
}
37+
38+
public void Dismiss(T result)
39+
{
40+
_tcs.TrySetResult(result);
41+
OnDismissed(result);
42+
}
43+
44+
public Task<T> Result => _tcs.Task;
45+
46+
public override void LightDismiss()
47+
{
48+
_tcs.TrySetResult(OnLightDismissed());
49+
}
50+
51+
protected virtual T OnLightDismissed()
52+
{
53+
return default(T);
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)