Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
BasePopupView.cs
1using System;
2using System.Threading.Tasks;
3using Microsoft.Maui.Controls;
4using Microsoft.Maui.Graphics;
6
8{
12 public class BasePopupView : ContentView, ILifeCycleView
13 {
14 public static readonly BindableProperty PopupContentProperty = BindableProperty.Create(
15 nameof(PopupContent),
16 typeof(View),
17 typeof(BasePopupView),
18 null,
19 propertyChanged: OnPopupContentChanged);
20
21 public static readonly BindableProperty PlacementProperty = BindableProperty.Create(
22 nameof(Placement),
23 typeof(PopupPlacement),
24 typeof(BasePopupView),
25 PopupPlacement.Center,
26 propertyChanged: OnLayoutPropertyChanged);
27
28 public static readonly BindableProperty AnchorPointProperty = BindableProperty.Create(
29 nameof(AnchorPoint),
30 typeof(Point?),
31 typeof(BasePopupView),
32 null,
33 propertyChanged: OnLayoutPropertyChanged);
34
35 public static readonly BindableProperty PopupMarginProperty = BindableProperty.Create(
36 nameof(PopupMargin),
37 typeof(Thickness),
38 typeof(BasePopupView),
39 new Thickness(0),
40 propertyChanged: OnLayoutPropertyChanged);
41
42 public static readonly BindableProperty PopupPaddingProperty = BindableProperty.Create(
43 nameof(PopupPadding),
44 typeof(Thickness),
45 typeof(BasePopupView),
46 new Thickness(0),
47 propertyChanged: OnPopupPaddingChanged);
48
49 public static readonly BindableProperty CloseOnBackgroundTapProperty = BindableProperty.Create(
51 typeof(bool),
52 typeof(BasePopupView),
53 true);
54
55 public static readonly BindableProperty StretchContentWidthProperty = BindableProperty.Create(
56 nameof(StretchContentWidth),
57 typeof(bool),
58 typeof(BasePopupView),
59 false,
60 propertyChanged: OnLayoutPropertyChanged);
61
62 public static readonly BindableProperty StretchContentHeightProperty = BindableProperty.Create(
64 typeof(bool),
65 typeof(BasePopupView),
66 false,
67 propertyChanged: OnLayoutPropertyChanged);
68
69 private readonly AbsoluteLayout root;
70 private readonly ContentView popupContainer;
71
72 public event EventHandler? BackgroundTapped;
73
74 public BasePopupView()
75 {
76 this.HorizontalOptions = LayoutOptions.Fill;
77 this.VerticalOptions = LayoutOptions.Fill;
78
79 this.popupContainer = new ContentView
80 {
81 Padding = this.PopupPadding
82 };
83
84 this.root = new AbsoluteLayout
85 {
86 HorizontalOptions = LayoutOptions.Fill,
87 VerticalOptions = LayoutOptions.Fill
88 };
89
90 // Separate transparent background view to capture outside taps without swallowing child gestures.
91 BoxView background = new BoxView
92 {
93 BackgroundColor = Colors.Transparent,
94 HorizontalOptions = LayoutOptions.Fill,
95 VerticalOptions = LayoutOptions.Fill
96 };
97 AbsoluteLayout.SetLayoutBounds(background, new Rect(0, 0, 1, 1));
98 AbsoluteLayout.SetLayoutFlags(background, Microsoft.Maui.Layouts.AbsoluteLayoutFlags.All);
99
100 TapGestureRecognizer backgroundTap = new();
101 backgroundTap.Tapped += this.OnPopupBackgroundTapped;
102 background.GestureRecognizers.Add(backgroundTap);
103
104 this.root.Children.Add(background);
105
106 AbsoluteLayout.SetLayoutFlags(this.popupContainer, Microsoft.Maui.Layouts.AbsoluteLayoutFlags.None);
107 this.root.Children.Add(this.popupContainer);
108
109 base.Content = this.root;
110 }
111
115 public View? PopupContent
116 {
117 get => (View?)this.GetValue(PopupContentProperty);
118 set => this.SetValue(PopupContentProperty, value);
119 }
120
124 public PopupPlacement Placement
125 {
126 get => (PopupPlacement)this.GetValue(PlacementProperty);
127 set => this.SetValue(PlacementProperty, value);
128 }
129
133 public Point? AnchorPoint
134 {
135 get => (Point?)this.GetValue(AnchorPointProperty);
136 set => this.SetValue(AnchorPointProperty, value);
137 }
138
142 public Thickness PopupMargin
143 {
144 get => (Thickness)this.GetValue(PopupMarginProperty);
145 set => this.SetValue(PopupMarginProperty, value);
146 }
147
151 public Thickness PopupPadding
152 {
153 get => (Thickness)this.GetValue(PopupPaddingProperty);
154 set => this.SetValue(PopupPaddingProperty, value);
155 }
156
161 {
162 get => (bool)this.GetValue(CloseOnBackgroundTapProperty);
163 set => this.SetValue(CloseOnBackgroundTapProperty, value);
164 }
165
170 {
171 get => (bool)this.GetValue(StretchContentWidthProperty);
172 set => this.SetValue(StretchContentWidthProperty, value);
173 }
174
179 {
180 get => (bool)this.GetValue(StretchContentHeightProperty);
181 set => this.SetValue(StretchContentHeightProperty, value);
182 }
183
188 {
189 this.BackgroundTapped?.Invoke(this, EventArgs.Empty);
190 }
191
192 public virtual Task OnInitializeAsync()
193 {
194 return Task.CompletedTask;
195 }
196
197 public virtual Task OnDisposeAsync()
198 {
199 return Task.CompletedTask;
200 }
201
202 public virtual Task OnAppearingAsync()
203 {
204 return Task.CompletedTask;
205 }
206
207 public virtual Task OnDisappearingAsync()
208 {
209 return Task.CompletedTask;
210 }
211
212 protected override void OnSizeAllocated(double width, double height)
213 {
214 base.OnSizeAllocated(width, height);
215 this.UpdatePopupLayout(width, height);
216 }
217
218 private static void OnPopupContentChanged(BindableObject bindable, object? oldValue, object? newValue)
219 {
220 if (bindable is BasePopupView popupView)
221 {
222 popupView.popupContainer.Content = newValue as View;
223 popupView.InvalidateMeasure();
224 popupView.UpdatePopupLayout(popupView.Width, popupView.Height);
225 }
226 }
227
228 private static void OnLayoutPropertyChanged(BindableObject bindable, object? oldValue, object? newValue)
229 {
230 if (bindable is BasePopupView popupView)
231 popupView.UpdatePopupLayout(popupView.Width, popupView.Height);
232 }
233
234 private static void OnPopupPaddingChanged(BindableObject bindable, object? oldValue, object? newValue)
235 {
236 if (bindable is BasePopupView popupView && newValue is Thickness padding)
237 {
238 popupView.popupContainer.Padding = padding;
239 popupView.InvalidateMeasure();
240 popupView.UpdatePopupLayout(popupView.Width, popupView.Height);
241 }
242 }
243
244 private void OnPopupBackgroundTapped(object? sender, TappedEventArgs e)
245 {
246 if (!this.CloseOnBackgroundTap)
247 return;
248
249 if (this.popupContainer.Width > 0 && this.popupContainer.Height > 0)
250 {
251 Point? point = e.GetPosition(this.popupContainer);
252 if (point is Point hit &&
253 hit.X >= 0 && hit.Y >= 0 &&
254 hit.X <= this.popupContainer.Width &&
255 hit.Y <= this.popupContainer.Height)
256 {
257 return;
258 }
259 }
260
261 this.BackgroundTapped?.Invoke(this, EventArgs.Empty);
262 }
263
264 private void UpdatePopupLayout(double width, double height)
265 {
266 if (width <= 0 || height <= 0)
267 return;
268
269 Thickness margin = this.PopupMargin;
270 double availableWidth = Math.Max(0, width - margin.Left - margin.Right);
271 double availableHeight = Math.Max(0, height - margin.Top - margin.Bottom);
272
273 if (availableWidth <= 0 || availableHeight <= 0)
274 {
275 AbsoluteLayout.SetLayoutBounds(this.popupContainer, new Rect(margin.Left, margin.Top, Math.Max(0, availableWidth), Math.Max(0, availableHeight)));
276 this.root.InvalidateMeasure();
277 return;
278 }
279
280 Size request = this.popupContainer.Measure(availableWidth, availableHeight);
281 double contentWidth = double.IsInfinity(request.Width) || request.Width <= 0 ? availableWidth : Math.Min(request.Width, availableWidth);
282 double contentHeight = double.IsInfinity(request.Height) || request.Height <= 0 ? availableHeight : Math.Min(request.Height, availableHeight);
283
284 if (this.Placement == PopupPlacement.Fill)
285 {
286 contentWidth = availableWidth;
287 contentHeight = availableHeight;
288 }
289 else
290 {
291 if (this.StretchContentWidth)
292 contentWidth = availableWidth;
293 if (this.StretchContentHeight)
294 contentHeight = availableHeight;
295 }
296
297 double x = margin.Left;
298 double y = margin.Top;
299
300 switch (this.Placement)
301 {
302 case PopupPlacement.Center:
303 x += (availableWidth - contentWidth) / 2;
304 y += (availableHeight - contentHeight) / 2;
305 break;
306 case PopupPlacement.Top:
307 x += (availableWidth - contentWidth) / 2;
308 break;
309 case PopupPlacement.Bottom:
310 x += (availableWidth - contentWidth) / 2;
311 y = height - margin.Bottom - contentHeight;
312 break;
313 case PopupPlacement.Left:
314 y += (availableHeight - contentHeight) / 2;
315 break;
316 case PopupPlacement.Right:
317 x = width - margin.Right - contentWidth;
318 y += (availableHeight - contentHeight) / 2;
319 break;
320 case PopupPlacement.Anchor:
321 Point anchor = this.AnchorPoint ?? new Point(width / 2, height / 2);
322 x = anchor.X - (contentWidth / 2);
323 y = anchor.Y - (contentHeight / 2);
324 double minX = margin.Left;
325 double maxX = width - margin.Right - contentWidth;
326 double minY = margin.Top;
327 double maxY = height - margin.Bottom - contentHeight;
328 x = Math.Min(Math.Max(x, minX), maxX);
329 y = Math.Min(Math.Max(y, minY), maxY);
330 break;
331 case PopupPlacement.Fill:
332 x = margin.Left;
333 y = margin.Top;
334 break;
335 case PopupPlacement.TopLeft:
336 x = margin.Left;
337 y = margin.Top;
338 break;
339 case PopupPlacement.TopRight:
340 x = width - margin.Right - contentWidth;
341 y = margin.Top;
342 break;
343 case PopupPlacement.BottomLeft:
344 x = margin.Left;
345 y = height - margin.Bottom - contentHeight;
346 break;
347 case PopupPlacement.BottomRight:
348 x = width - margin.Right - contentWidth;
349 y = height - margin.Bottom - contentHeight;
350 break;
351 default:
352 x += (availableWidth - contentWidth) / 2;
353 y += (availableHeight - contentHeight) / 2;
354 break;
355 }
356
357 AbsoluteLayout.SetLayoutBounds(this.popupContainer, new Rect(x, y, contentWidth, contentHeight));
358 this.root.InvalidateMeasure();
359 }
360
361 protected override void OnPropertyChanged(string? propertyName = null)
362 {
363 base.OnPropertyChanged(propertyName);
364 if (propertyName == nameof(this.Content) && base.Content is View content && !ReferenceEquals(content, this.root))
365 {
366 base.Content = this.root;
367 this.PopupContent = content;
368 }
369 }
370 }
371}
Flexible popup surface capable of positioning its content in different regions of the screen.
Thickness PopupPadding
Gets or sets padding applied inside the popup container.
bool StretchContentWidth
Gets or sets if popup content should stretch to use the available width inside margins.
virtual Task OnDisappearingAsync()
Method called when view is disappearing from the screen.
View? PopupContent
Gets or sets the view rendered inside the popup container.
Thickness PopupMargin
Gets or sets the margin reserved around the popup.
virtual Task OnAppearingAsync()
Method called when view is appearing on the screen.
virtual Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
virtual Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
Point? AnchorPoint
Gets or sets the anchor point for PopupPlacement.Anchor.
bool CloseOnBackgroundTap
Gets or sets if taps outside popup content should raise BackgroundTapped.
bool StretchContentHeight
Gets or sets if popup content should stretch to use the available height inside margins.
PopupPlacement Placement
Gets or sets the general placement strategy for the popup.
void TriggerBackgroundTapped()
Manually triggers the background tapped event.
Interface for views who need to react to life-cycle events.