* android and ios popup implementation

* bring renderer

* Moved Handlers to Core project

* updated Windows supported version

* Added Handlers, native popup and Extensions to Core project

* Added workaround classes until p12

* Added NavigationExtensions and Popup implementation

* Added workaround until p12

* Updated Core to p12

* p12 update

* returned with the WrapperControl

* Handler work

* initial spike on PlatformSpecificConfiguration and Handlers

* Fixed popupColor on iOS

* more iOS love

* Changed the method call to be one from internal MAUI

* IsLightDismissEnabled is now bindable

* IsLightDismissEnabled is now a Bindable Property

* Ported all sample pages from XCT to MCT

* hold for rebase

* Added popup samples

* adjusted to workaround a maui bug

* changed the obsolete API to a supported one

* Fixed windows popup version

* code style

* Added xml docs and code improvements

* More xml docs and code clean up

* Fixed sample

* Fixed border and size calculation on Windows

* more native work

* samples!

* fixed editor config

* Update src/CommunityToolkit.Maui.Core/Platform/Android/PopupExtensions.android.cs

Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>

* Update src/CommunityToolkit.Maui.Core/Platform/Windows/MCTPopup.windows.cs

Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>

* Update src/CommunityToolkit.Maui.Core/Platform/Windows/PopupExtensions.windows.cs

Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>

* Update src/CommunityToolkit.Maui/Extensions/Navigation/NavigationExtensions.netstandard.cs

Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>

* Update src/CommunityToolkit.Maui/Extensions/Navigation/NavigationExtensions.netstandard.cs

Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>

* moved files and updated to p13

* Changed prop implementation on PopupSize

* bump windows version to match .NET MAUI template

* Replace all Xamarin references to MAUI
RIP Xamarin (◡︵◡)

* Code review

* removed the "native" word

* fixed wrong tag

* fixed ViewController set

* removed using

* make the navigation on Windows better

* Remove `netstandard`

* Fix `UniformItemsLayoutTests ` namespace

* Update PopupViewHandler.shared.cs

* Update PopupViewHandler.shared.cs

* Implement Shell and Dependency Injection for Popup Sample App (#303)

* Alphabetize FlyoutItems

* Reorganize Folder Architecture, Create ViewsGallery, Use AppShell

* Fix Build Errors

* Fix Build Errors

* Update CommunityToolkit.Maui.Sample.csproj (#302)

* Update Shell + DI

* Add Popups to `MultiplePopupPage`

* Finish `MultiplePopupPage`

* Update MultiplePopupPage.xaml.cs

* Update `AppShell.xaml` for MacCatalyst

* Update `Microsoft.NET.Test.Sdk ` NuGet Package

* Rename to `PopupExtensions.*.cs`

* Update Code Organization

* Update Code Organization

* Create PopupTests.cs

* Rename `MCTPopup` to `MauiPopup`

This aligns with the .NET MAUI naming convention, e.g. `MauiLabel`

* Add `MacCatalyst` Values

* Added workaround for Windows measurement

* improved comment

* Update `Title`

* Center the red frame

* Use `ButtonPopup`

* Added a test as sample

* Fixed namespace and added xml docs

* Updated to use Page instead of INavigation

* Fixed measurement on Android

* Popup tests to make Brandon Happy  ƪ(ړײ)‎ƪ​​

* Update Exceptions

* Add `ShowPopupAsync`, `dotnet format`

* Removed MainPage reference from MauiPopup.macios

* Moved Maui.Controls reference to CommunityToolkit.Maui project

* code fix style

* Renamed BasePopup -> Popup

The control isn't abstract anymore

* deleted old Popup file

* measurement is hard  (ノ ゜Д゜)ノ ︵ ┻━┻

* Fix `CsharpBindingPopup`

* add `static`

* Move `WrapperControl` to `CommunityToolkit.Core.Views`

* Add Nullable Static Analyzers, Remove Null Forgiving Operator, Remove unused parameters

* Remove unneeded Null Propagating Operator

* `dotnet format`

* Remove unneeded Null Propagating Operators, Remove Unused Code

* Remove Unused Code

* Fix Null Crash

* Rename HandlerImpl to HandlerImplementation

* Move `PopupExtensions` to `Views/Popup`

* Rename `BasePopup`  to `Popup`

* Fix Typo

* Update PopupExtensions.shared.cs

* Update PopupExtensions.windows.cs

* Reorder methods, Set default Color from `null` to `Colors.Transparant`

* Update Namespaces

* Update MauiPopup.windows.cs

* Update src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupViewHandler.macios.cs

Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com>

* Add missing `;`

* Fixed popup issue for MacCatalyst

* Improved the measurement calculation

* Fixed samples

* Added netstandard rules on csproj

* moved non-platform code to a new file

* code style

* Update `ReturnResultPopup` Sample

* Moved wrapper control to CommunityToolkit.Maui project

* Removed WeakEventManager code

* Windows work to support the Wrapper change

* Fixed popup crash

* Remove `*.netstandard.cs` file suffix

The .NET MAUI Community Toolkit doesn't support .NET Standard; only .NET 6.0+ is supported.

We will defer this to the Discussion #301:
https://github.com/CommunityToolkit/Maui/discussions/301

* Remove Unused `MainGalleryViewModel`

* Remove Duplicate `PopupAnchorViewModel` + `PopupPositionViewModel`, Consolidate Under `Add Views View Models`

* Update `Add Extensions` Comment

* Update `MauiPopup` Naming

* Rename `BasePopup` to `Popup`

* Convert `public class MockApplication` to `class MockApplication` to match `ApplicationHandlerStub`

* Remove `netstandard`

* Us DI to Inject `CsharpBindingPopupViewModel`

* Update XML Comments for `LightDismiss`

* Rename to `IPopup.OnLightDismissed()` to match `IPopup.OnClosed()` and `IPopup.OnOpened()`

Since this is the method that runs _after_ a Light Dismiss has been triggered, it should use the `On*` naming and use the past-tense of `*Dismissed` to demonstrate that it is a reaction to an event that happened in the past

* Rename to `WasLightDismissed` to denote that the action has already happened by using the past-tense

* Fix OnLightDismissed for Windows

* Fix `CommunityToolkit.Core` namespace -> `CommunityToolkit.Maui.Core`

* Removed border and fixed double event subscription

* added xmlns namespace for Views namespace

* Changed the default color

* Added layouts to xml schema

* Consolidate Fields + Properties together

* Consolidate Fields + Properties Together

* Rename `PopupViewHandler` to `PopupHandler`

* Replace Light Dismiss Nomenclature (#324)

* Update `CommunityToolkit.Mvvm` NuGet Pacakge

* Resolve Recommendation from @VladislavAntonyuk

https://github.com/CommunityToolkit/Maui/pull/290/files#r817959288

Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com>
This commit is contained in:
Pedro Jesus 2022-03-14 18:19:57 -03:00 коммит произвёл GitHub
Родитель c30f03d95c
Коммит c1786d6481
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
75 изменённых файлов: 3502 добавлений и 49 удалений

Просмотреть файл

@ -80,7 +80,7 @@ In general, this project will have all the basement to develop our Toolkit, incl
Here we will have some:
- BaseViews, could be Views that will be used by other Views, like PaddingButton (that's used by Snackbar) or the MCTPopup that will be a native control implemented in a way that can work with our handler. This same approach is used here
- BaseViews, could be Views that will be used by other Views, like PaddingButton (that's used by Snackbar) or the MauiPopup (used by Popup) that will be a native control implemented in a way that can work with our handler. This same approach is used here
- Primitives, which will be base types that can be used by everyone, like our MathOperator. So other frameworks may not have the concept of Behavior or Converter but they can mimic them as helper classes/methods and use our primitives.

Просмотреть файл

@ -4,11 +4,13 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!--CS1570: XML comment has badly formed XML 'Expected an end tag for element [parameter] -->
<!--CS1571: XML comment on [construct] has a duplicate param tag for [parameter] -->
<!--CS1572: XML comment has a param tag for '[parameter]', but there is no parameter by that name -->
<!--CS1573: Parameter has no matching param tag in the XML comment -->
<!--CS1574: XML comment has cref attribute that could not be resolved-->
<!--CS1734: XML comment has a paramref tag, but there is no parameter by that name-->
<WarningsAsErrors>nullable,CS1572,CS1573,CS1574,CS1734</WarningsAsErrors>
<WarningsAsErrors>nullable,CS1570,CS1571,CS1572,CS1573,CS1574,CS1734</WarningsAsErrors>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>

Просмотреть файл

@ -9,6 +9,7 @@
xmlns:extensions="clr-namespace:CommunityToolkit.Maui.Sample.Pages.Extensions"
xmlns:layouts="clr-namespace:CommunityToolkit.Maui.Sample.Pages.Layouts"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:views="clr-namespace:CommunityToolkit.Maui.Sample.Pages.Views"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Shell.FlyoutHeader>
@ -61,6 +62,12 @@
<ShellContent ContentTemplate="{DataTemplate layouts:LayoutsGalleryPage}" />
</FlyoutItem>
<FlyoutItem Title="Views"
Route="ViewsGalleryPage"
Icon="{OnPlatform Default='dotnet_bot.png', MacCatalyst=''}">
<ShellContent ContentTemplate="{DataTemplate views:ViewsGalleryPage}" />
</FlyoutItem>
<Shell.FlyoutFooter>
<Label Padding="4"
HorizontalOptions="End"

Просмотреть файл

@ -4,11 +4,13 @@ using CommunityToolkit.Maui.Sample.Pages.Behaviors;
using CommunityToolkit.Maui.Sample.Pages.Converters;
using CommunityToolkit.Maui.Sample.Pages.Extensions;
using CommunityToolkit.Maui.Sample.Pages.Layouts;
using CommunityToolkit.Maui.Sample.Pages.Views;
using CommunityToolkit.Maui.Sample.ViewModels;
using CommunityToolkit.Maui.Sample.ViewModels.Alerts;
using CommunityToolkit.Maui.Sample.ViewModels.Behaviors;
using CommunityToolkit.Maui.Sample.ViewModels.Converters;
using CommunityToolkit.Maui.Sample.ViewModels.Layouts;
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample;
@ -70,6 +72,11 @@ public partial class AppShell : Shell
// Add Layouts View Models
CreateViewModelMapping<UniformItemsLayoutPage, UniformItemsLayoutViewModel, LayoutsGalleryPage, LayoutsGalleryViewModel>(),
// Add Views View Models
CreateViewModelMapping<MultiplePopupPage, MultiplePopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupAnchorPage, PopupAnchorViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupPositionPage, PopupPositionViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
});
public AppShell()

Просмотреть файл

@ -37,7 +37,7 @@
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0-preview1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0-preview2" />
<PackageReference Include="CommunityToolkit.Maui.Markup" Version="1.0.0-pre7" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
@ -64,6 +64,7 @@
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<PropertyGroup Condition="$(TargetFramework.Contains('-windows'))">
<OutputType>WinExe</OutputType>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>

Просмотреть файл

@ -2,6 +2,7 @@
using CommunityToolkit.Maui.Sample.ViewModels.Alerts;
using CommunityToolkit.Maui.Sample.ViewModels.Behaviors;
using CommunityToolkit.Maui.Sample.ViewModels.Converters;
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Sample.ViewModels.Layouts;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
@ -24,7 +25,7 @@ public static class MauiProgram
builder.Services.AddTransient<ConvertersGalleryViewModel>();
builder.Services.AddTransient<ExtensionsGalleryViewModel>();
builder.Services.AddTransient<LayoutsGalleryViewModel>();
builder.Services.AddTransient<MainGalleryViewModel>();
builder.Services.AddTransient<ViewsGalleryViewModel>();
// Add Alerts View Models
builder.Services.AddTransient<SnackbarViewModel>();
@ -75,12 +76,19 @@ public static class MauiProgram
builder.Services.AddTransient<TextCaseConverterViewModel>();
builder.Services.AddTransient<VariableMultiValueConverterViewModel>();
// Add Extensions
// Add Extensions View Models
builder.Services.AddTransient<ColorAnimationExtensionsViewModel>();
// Add Layouts
// Add Layouts View Models
builder.Services.AddTransient<UniformItemsLayoutViewModel>();
// Add Views View Models
builder.Services.AddTransient<CsharpBindingPopupViewModel>();
builder.Services.AddTransient<MultiplePopupViewModel>();
builder.Services.AddTransient<PopupAnchorViewModel>();
builder.Services.AddTransient<PopupPositionViewModel>();
builder.Services.AddTransient<XamlBindingPopupViewModel>();
return builder.Build();
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
namespace CommunityToolkit.Maui.Sample.Models;
static class PopupSizeConstants
{
// examples for fixed sizes
public static Size Tiny { get; } = new(100, 100);
public static Size Small { get; } = new(300, 300);
// examples for relative to screen sizes
public static Size Medium { get; } = new(0.7 * (DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density), 0.6 * (DeviceDisplay.MainDisplayInfo.Height / DeviceDisplay.MainDisplayInfo.Density));
public static Size Large { get; } = new(0.9 * (DeviceDisplay.MainDisplayInfo.Width / DeviceDisplay.MainDisplayInfo.Density), 0.8 * (DeviceDisplay.MainDisplayInfo.Height / DeviceDisplay.MainDisplayInfo.Density));
}

Просмотреть файл

@ -32,4 +32,4 @@ public sealed class SectionModel
public string Description { get; }
public Color Color { get; }
}
}

Просмотреть файл

@ -1,6 +1,7 @@
using CommunityToolkit.Maui.Markup;
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Sample.ViewModels;
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using static CommunityToolkit.Maui.Markup.GridRowsColumns;
using Application = Microsoft.Maui.Controls.Application;
@ -35,10 +36,12 @@ public abstract class BaseGalleryPage<TViewModel> : BasePage<TViewModel> where T
var collectionView = (CollectionView)sender;
collectionView.SelectedItem = null;
if (e.CurrentSelection.FirstOrDefault() is SectionModel sectionModel)
if (e.CurrentSelection.FirstOrDefault() is not SectionModel sectionModel)
{
await Shell.Current.GoToAsync(AppShell.GetPageRoute(sectionModel.ViewModelType));
return;
}
await Shell.Current.GoToAsync(AppShell.GetPageRoute(sectionModel.ViewModelType));
}
class GalleryDataTemplate : DataTemplate

Просмотреть файл

@ -0,0 +1,36 @@
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.MultiplePopupPage"
Title="MultiplePopupPage"
x:TypeArguments="viewModels:MultiplePopupViewModel"
x:DataType="viewModels:MultiplePopupViewModel">
<ContentPage.Content>
<ScrollView>
<VerticalStackLayout Spacing="12">
<Button Text="Simple Popup" Clicked="HandleSimplePopupButtonClicked" />
<Button Text="Button Popup" Clicked="HandleButtonPopupButtonClicked" />
<Button Text="Multiple Button Popup" Clicked="HandleMultipleButtonPopupButtonClicked" />
<Button Text="Cannot Be Dismissed By Tapping Outside Popup" Clicked="HandleNoOutsideTapDismissPopupClicked" />
<Button Text="Toggle Size Popup" Clicked="HandleToggleSizePopupButtonClicked" />
<Button Text="Transparent Popup" Clicked="HandleTransparentPopupButtonClicked" />
<Button Text="Opened Event Simple Popup" Clicked="HandleOpenedEventSimplePopupButtonClicked" />
<Button Text="Handle Return Result Popup" Clicked="HandleReturnResultPopupButtonClicked" />
<Button Text="XAML Binding Popup" Clicked="HandleXamlBindingPopupPopupButtonClicked" />
<Button Text="C# Binding Popup" Clicked="HandleCsharpBindingPopupButtonClicked" />
</VerticalStackLayout>
</ScrollView>
</ContentPage.Content>
</pages:BasePage>

Просмотреть файл

@ -0,0 +1,78 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class MultiplePopupPage : BasePage<MultiplePopupViewModel>
{
readonly CsharpBindingPopupViewModel csharpBindingPopupViewModel;
public MultiplePopupPage(MultiplePopupViewModel multiplePopupViewModel, CsharpBindingPopupViewModel csharpBindingPopupViewModel)
: base(multiplePopupViewModel)
{
InitializeComponent();
this.csharpBindingPopupViewModel = csharpBindingPopupViewModel;
}
async void HandleSimplePopupButtonClicked(object sender, EventArgs e)
{
var simplePopup = new SimplePopup();
await this.ShowPopupAsync(simplePopup);
}
async void HandleButtonPopupButtonClicked(object sender, EventArgs e)
{
var buttonPopup = new ButtonPopup();
await this.ShowPopupAsync(buttonPopup);
}
async void HandleMultipleButtonPopupButtonClicked(object sender, EventArgs e)
{
var multipleButtonPopup = new MultipleButtonPopup();
await this.ShowPopupAsync(multipleButtonPopup);
}
async void HandleNoOutsideTapDismissPopupClicked(object sender, EventArgs e)
{
var noOutsideTapDismissPopup = new NoOutsideTapDismissPopup();
await this.ShowPopupAsync(noOutsideTapDismissPopup);
}
async void HandleToggleSizePopupButtonClicked(object sender, EventArgs e)
{
var toggleSizePopup = new ToggleSizePopup();
await this.ShowPopupAsync(toggleSizePopup);
}
async void HandleTransparentPopupButtonClicked(object sender, EventArgs e)
{
var transparentPopup = new TransparentPopup();
await this.ShowPopupAsync(transparentPopup);
}
async void HandleOpenedEventSimplePopupButtonClicked(object sender, EventArgs e)
{
var openedEventSimplePopup = new OpenedEventSimplePopup();
await this.ShowPopupAsync(openedEventSimplePopup);
}
async void HandleReturnResultPopupButtonClicked(object sender, EventArgs e)
{
var returnResultPopup = new ReturnResultPopup();
var result = await this.ShowPopupAsync(returnResultPopup);
await DisplayAlert("Pop Result Returned", $"Result: {result}", "OK");
}
async void HandleXamlBindingPopupPopupButtonClicked(object sender, EventArgs e)
{
var xamlBindingPopup = new XamlBindingPopup();
await this.ShowPopupAsync(xamlBindingPopup);
}
async void HandleCsharpBindingPopupButtonClicked(object sender, EventArgs e)
{
var csharpBindingPopup = new CsharpBindingPopup(csharpBindingPopupViewModel);
await this.ShowPopupAsync(csharpBindingPopup);
}
}

Просмотреть файл

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.PopupAnchorPage"
Title="PopupAnchorPage"
x:TypeArguments="viewModels:PopupAnchorViewModel"
x:DataType="viewModels:PopupAnchorViewModel">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="BaseLabel" TargetType="Label">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
</Style>
<Style x:Key="Header" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
<Setter Property="Margin" Value="15, 10" />
</Style>
<Style x:Key="Indicator" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
<Setter Property="FontSize" Value="24" />
</Style>
<Style TargetType="Button">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<VerticalStackLayout>
<Label Style="{StaticResource Header}"
Text="Popup's can be anchored to other Views on the page. Tap and drag the X to change where the popup will be centered." />
<Label x:Name="Indicator"
Style="{StaticResource Indicator}"
Text="❌">
<Label.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated" />
</Label.GestureRecognizers>
</Label>
<Button Text="Show Popup"
Command="{Binding ShowPopup}"
CommandParameter="{x:Reference Indicator}"/>
</VerticalStackLayout>
</pages:BasePage>

Просмотреть файл

@ -0,0 +1,40 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class PopupAnchorPage : BasePage<PopupAnchorViewModel>
{
public PopupAnchorPage(PopupAnchorViewModel popupAnchorViewModel)
: base(popupAnchorViewModel)
{
InitializeComponent();
Indicator ??= new();
}
void OnPanUpdated(object? sender, PanUpdatedEventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
var label = (Label)sender;
if (Device.RuntimePlatform is Device.Android)
{
label.TranslationX += e.TotalX;
label.TranslationY += e.TotalY;
}
else
{
switch (e.StatusType)
{
case GestureStatus.Running:
label.TranslationX = e.TotalX;
label.TranslationY = e.TotalY;
break;
case GestureStatus.Completed:
label.TranslationX += e.TotalX;
label.TranslationY += e.TotalY;
break;
}
}
}
}

Просмотреть файл

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage x:Class="CommunityToolkit.Maui.Sample.Pages.Views.PopupPositionPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:TypeArguments="viewModels:PopupPositionViewModel"
x:DataType="viewModels:PopupPositionViewModel"
Title="PopupPositionPage"
BackgroundColor="White">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="Header"
TargetType="Label">
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
<Setter Property="Margin" Value="15, 10" />
</Style>
<Style TargetType="ScrollView">
<Setter Property="VerticalOptions" Value="FillAndExpand" />
</Style>
<Style x:Key="ItemsLayout"
TargetType="StackLayout">
<Setter Property="Spacing" Value="16" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView>
<Grid ColumnDefinitions="Auto, Auto, Auto"
HorizontalOptions="CenterAndExpand"
RowDefinitions="Auto, Auto, Auto, Auto"
VerticalOptions="CenterAndExpand">
<Label Grid.Row="0"
Grid.ColumnSpan="3"
Style="{StaticResource Header}"
Text="Popup's can be positioned anywhere on the screen using VerticalOptions and HorizontalOptions. Tap the arrows below to see how this works." />
<Button Grid.Row="1"
Grid.Column="0"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.TopLeft}"
FontSize="18"
Text="&#x2196;" />
<Button Grid.Row="1"
Grid.Column="1"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.Top}"
FontSize="18"
Text="&#x2191;" />
<Button Grid.Row="1"
Grid.Column="2"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.TopRight}"
FontSize="18"
Text="&#x2197;" />
<Button Grid.Row="2"
Grid.Column="0"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.Left}"
FontSize="18"
Text="&#x2190;" />
<Button Grid.Row="2"
Grid.Column="1"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.Center}"
FontSize="18"
Text="&#x26AC;" />
<Button Grid.Row="2"
Grid.Column="2"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.Right}"
FontSize="18"
Text="&#x2192;" />
<Button Grid.Row="3"
Grid.Column="0"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.BottomLeft}"
FontSize="18"
Text="&#x2199;" />
<Button Grid.Row="3"
Grid.Column="1"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.Bottom}"
FontSize="18"
Text="&#x2193;" />
<Button Grid.Row="3"
Grid.Column="2"
Command="{Binding DisplayPopup}"
CommandParameter="{x:Static viewModels:PopupPositionViewModel+PopupPosition.BottomRight}"
FontSize="18"
Text="&#x2198;" />
</Grid>
</ScrollView>
</pages:BasePage>

Просмотреть файл

@ -0,0 +1,12 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class PopupPositionPage : BasePage<PopupPositionViewModel>
{
public PopupPositionPage(PopupPositionViewModel popupPositionViewModel)
: base(popupPositionViewModel)
{
InitializeComponent();
}
}

Просмотреть файл

@ -0,0 +1,10 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public class ViewsGalleryPage : BaseGalleryPage<ViewsGalleryViewModel>
{
public ViewsGalleryPage(ViewsGalleryViewModel viewsGalleryViewModel) : base("Views", viewsGalleryViewModel)
{
}
}

Просмотреть файл

@ -1,31 +0,0 @@
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Sample.ViewModels.Alerts;
using CommunityToolkit.Maui.Sample.ViewModels.Behaviors;
using CommunityToolkit.Maui.Sample.ViewModels.Converters;
using CommunityToolkit.Maui.Sample.ViewModels.Layouts;
namespace CommunityToolkit.Maui.Sample.ViewModels;
public class MainGalleryViewModel : BaseGalleryViewModel
{
public MainGalleryViewModel()
: base(new[]
{
SectionModel.Create<AlertsGalleryViewModel>("Alerts", Color.FromArgb("#EF6950"),
"Alerts allow you display alerts to your user"),
SectionModel.Create<BehaviorsGalleryViewModel>("Behaviors", Color.FromArgb("#8E8CD8"),
"Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code"),
SectionModel.Create<ConvertersGalleryViewModel>("Converters", Color.FromArgb("#EA005E"),
"Converters let you convert bindings of a certain type to a different value, based on custom logic"),
SectionModel.Create<ExtensionsGalleryViewModel>("Extensions", Color.FromArgb("#00EA56"),
"Extensions lets you add methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type."),
SectionModel.Create<LayoutsGalleryViewModel>("Layouts", Color.FromArgb("#00EA56"),
"Layouts"),
})
{
}
}

Просмотреть файл

@ -0,0 +1,8 @@
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public sealed class CsharpBindingPopupViewModel
{
public string Title { get; } = "C# Binding Popup";
public string Message { get; } = "This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls.";
}

Просмотреть файл

@ -0,0 +1,8 @@
using System;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public class MultiplePopupViewModel : BaseViewModel
{
}

Просмотреть файл

@ -0,0 +1,39 @@
using System.Windows.Input;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public sealed class PopupAnchorViewModel : BaseViewModel
{
public PopupAnchorViewModel()
{
ShowPopup = new Command<View>(OnShowPopup);
}
public ICommand ShowPopup { get; }
static Page Page => Application.Current?.MainPage ?? throw new NullReferenceException();
void OnShowPopup(View anchor)
{
// Using the C# version of Popup until this get fixed
// https://github.com/dotnet/maui/issues/4300
// This works
var popup = new TransparentPopupCSharp()
{
Anchor = anchor
};
// This doesn't work
//var popup = new TransparentPopup
//{
// Anchor = anchor
//};
Page.ShowPopup(popup);
}
}

Просмотреть файл

@ -0,0 +1,79 @@
using System.Windows.Input;
using CommunityToolkit.Maui.Views;
using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public class PopupPositionViewModel : BaseViewModel
{
public PopupPositionViewModel()
{
DisplayPopup = new Command<PopupPosition>(OnDisplayPopup);
}
public ICommand DisplayPopup { get; }
static Page Page => Application.Current?.MainPage ?? throw new NullReferenceException();
void OnDisplayPopup(PopupPosition position)
{
// Using the C# version of Popup until this get fixed
// https://github.com/dotnet/maui/issues/4300
var popup = new TransparentPopupCSharp();
switch (position)
{
case PopupPosition.TopLeft:
popup.VerticalOptions = LayoutAlignment.Start;
popup.HorizontalOptions = LayoutAlignment.Start;
break;
case PopupPosition.Top:
popup.VerticalOptions = LayoutAlignment.Start;
popup.HorizontalOptions = LayoutAlignment.Center;
break;
case PopupPosition.TopRight:
popup.VerticalOptions = LayoutAlignment.Start;
popup.HorizontalOptions = LayoutAlignment.End;
break;
case PopupPosition.Left:
popup.VerticalOptions = LayoutAlignment.Center;
popup.HorizontalOptions = LayoutAlignment.Start;
break;
case PopupPosition.Center:
popup.VerticalOptions = LayoutAlignment.Center;
popup.HorizontalOptions = LayoutAlignment.Center;
break;
case PopupPosition.Right:
popup.VerticalOptions = LayoutAlignment.Center;
popup.HorizontalOptions = LayoutAlignment.End;
break;
case PopupPosition.BottomLeft:
popup.VerticalOptions = LayoutAlignment.End;
popup.HorizontalOptions = LayoutAlignment.Start;
break;
case PopupPosition.Bottom:
popup.VerticalOptions = LayoutAlignment.End;
popup.HorizontalOptions = LayoutAlignment.Center;
break;
case PopupPosition.BottomRight:
popup.VerticalOptions = LayoutAlignment.End;
popup.HorizontalOptions = LayoutAlignment.End;
break;
}
Page.ShowPopup(popup);
}
public enum PopupPosition
{
TopLeft = 0,
Top = 1,
TopRight = 2,
Left = 3,
Center = 4,
Right = 5,
BottomLeft = 6,
Bottom = 7,
BottomRight = 8
}
}

Просмотреть файл

@ -0,0 +1,17 @@
using CommunityToolkit.Maui.Sample.Models;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public sealed class ViewsGalleryViewModel : BaseGalleryViewModel
{
public ViewsGalleryViewModel()
: base(new[]
{
SectionModel.Create<PopupPositionViewModel>("Custom Positioning Popup", Colors.Red, "Displays a basic popup anywhere on the screen using VerticalOptions and HorizontalOptions"),
SectionModel.Create<PopupAnchorViewModel>("Anchor Popup", Colors.Red, "Popups can be anchored to other view's on the screen"),
SectionModel.Create<MultiplePopupViewModel>("Mutiple Popups Page", Colors.Red, "A page demonstrating multiple different Popups"),
})
{
}
}

Просмотреть файл

@ -0,0 +1,8 @@
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public sealed class XamlBindingPopupViewModel
{
public string Title { get; } = "Xaml Binding Popup";
public string Message { get; } = "This is a platform specific popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% as each platform implementation look and feel, but still allows you to use your .NET MAUI Controls.";
}

Просмотреть файл

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup x:Class="CommunityToolkit.Maui.Sample.ButtonPopup"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title"
TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider"
TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content"
TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout"
TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
<Style x:Key="ConfirmButton"
TargetType="Button">
<Setter Property="VerticalOptions" Value="EndAndExpand" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="Button Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI controls." />
<Button Clicked="Button_Clicked"
Style="{StaticResource ConfirmButton}"
Text="OKAY" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,13 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class ButtonPopup : Popup
{
public ButtonPopup()
{
InitializeComponent();
}
void Button_Clicked(object? sender, System.EventArgs e) => Close();
}

Просмотреть файл

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.CsharpBindingPopup"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
Size="{x:Static models:PopupSizeConstants.Large}">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="{Binding Title}" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="{Binding Message}" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,13 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class CsharpBindingPopup : Popup
{
public CsharpBindingPopup(CsharpBindingPopupViewModel csharpBindingPopupViewModel)
{
InitializeComponent();
BindingContext = csharpBindingPopupViewModel;
}
}

Просмотреть файл

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.MultipleButtonPopup"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
<Style x:Key="CancelButton" TargetType="Button">
<Setter Property="Background" Value="#FFF" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Blue" />
<Setter Property="TextColor" Value="Blue" />
</Style>
<Style x:Key="ButtonGroup" TargetType="HorizontalStackLayout">
<Setter Property="VerticalOptions" Value="EndAndExpand" />
<Setter Property="HorizontalOptions" Value="CenterAndExpand" />
<Setter Property="Spacing" Value="20" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="Button Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls." />
<HorizontalStackLayout Style="{StaticResource ButtonGroup}">
<Button Text="Cancel"
Style="{StaticResource CancelButton}"
Clicked="Cancel_Clicked" />
<Button Text="OKAY"
Clicked="Okay_Clicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,15 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class MultipleButtonPopup : Popup
{
public MultipleButtonPopup()
{
InitializeComponent();
}
void Cancel_Clicked(object? sender, System.EventArgs e) => Close(false);
void Okay_Clicked(object? sender, System.EventArgs e) => Close(true);
}

Просмотреть файл

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup x:Class="CommunityToolkit.Maui.Sample.NoOutsideTapDismissPopup"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
CanBeDismissedByTappingOutsideOfPopup="False"
Size="{x:Static models:PopupSizeConstants.Medium}">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title"
TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider"
TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content"
TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout"
TargetType="VerticalStackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="Simple Popup That Cannot Be Dismissed By Tapping Outside of the Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="{OnPlatform Android='This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls.',
iOS='This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls.',
UWP='UWP Flyouts do not have native support for toggling light dismiss mode. On UWP this will disrupt the closing of the flyout if you tap outside of the control'}" />
<Button Clicked="Button_Clicked"
Text="Close"
VerticalOptions="EndAndExpand" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,13 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class NoOutsideTapDismissPopup : Popup
{
public NoOutsideTapDismissPopup()
{
InitializeComponent();
}
void Button_Clicked(object? sender, System.EventArgs e) => Close();
}

Просмотреть файл

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.OpenedEventSimplePopup"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
Size="{x:Static models:PopupSizeConstants.Medium}"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label x:Name="Title"
Style="{StaticResource Title}" />
<BoxView Style="{StaticResource Divider}" />
<Label x:Name="Message"
Style="{StaticResource Content}" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,26 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class OpenedEventSimplePopup : Popup
{
public OpenedEventSimplePopup()
{
InitializeComponent();
Title ??= new();
Message ??= new();
Opened += OnOpened;
}
void OnOpened(object? sender, PopupOpenedEventArgs e)
{
Opened -= OnOpened;
Title.Text = "Opened Event Popup";
Message.Text = "The content of this popup was updated after the popup was rendered";
}
}

Просмотреть файл

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.ReturnResultPopup"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
Size="{x:Static models:PopupSizeConstants.Medium}">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
<Style x:Key="ConfirmButton" TargetType="Button">
<Setter Property="VerticalOptions" Value="EndAndExpand" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="Return Result Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="This popup returns a value when it is dismissed. The value varies depending if you tap on the CLOSE button or dismis the popup by tapping outside of it's bounds." />
<Button Text="CLOSE"
Style="{StaticResource ConfirmButton}"
Clicked="Button_Clicked" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,14 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class ReturnResultPopup : Popup
{
public ReturnResultPopup()
{
InitializeComponent();
ResultWhenUserTapsOutsideOfPopup = "User Tapped Outside of Popup";
}
void Button_Clicked(object? sender, EventArgs e) => Close("Close button tapped");
}

Просмотреть файл

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup
x:Class="CommunityToolkit.Maui.Sample.SimplePopup"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
Size="{x:Static models:PopupSizeConstants.Small}">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}" Text="Simple Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}" Text="This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls." />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,8 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class SimplePopup : Popup
{
public SimplePopup() => InitializeComponent();
}

Просмотреть файл

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.ToggleSizePopup"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
Size="{x:Static models:PopupSizeConstants.Medium}">
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
<Style x:Key="ConfirmButton" TargetType="Button">
<Setter Property="VerticalOptions" Value="EndAndExpand" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="Button Popup" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="This is a native popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your .NET MAUI Controls." />
<Button Text="Toggle Size"
Style="{StaticResource ConfirmButton}"
Clicked="Button_Clicked" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,26 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class ToggleSizePopup : Popup
{
readonly Size originalSize;
public ToggleSizePopup()
{
InitializeComponent();
originalSize = Size;
}
void Button_Clicked(object? sender, System.EventArgs e)
{
if (originalSize == Size)
{
Size = new Size(originalSize.Width * 1.25, originalSize.Height * 1.25);
}
else
{
Size = originalSize;
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup
x:Class="CommunityToolkit.Maui.Sample.TransparentPopup"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
Size="{x:Static models:PopupSizeConstants.Small}">
<Frame
BackgroundColor="Red"
HorizontalOptions="Center"
VerticalOptions="Center"
CornerRadius="{OnPlatform Default='25',
Android='50',
UWP='50'}"
HeightRequest="{OnPlatform Default='50',
Android='100'}"
WidthRequest="{OnPlatform Default='50',
Android='100'}" />
</mct:Popup>

Просмотреть файл

@ -0,0 +1,8 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class TransparentPopup : Popup
{
public TransparentPopup() => InitializeComponent();
}

Просмотреть файл

@ -0,0 +1,23 @@
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public class TransparentPopupCSharp : Popup
{
public TransparentPopupCSharp(Size popupSize)
:this()
{
Size = popupSize;
}
public TransparentPopupCSharp()
{
Content = new Frame
{
CornerRadius = 25,
HeightRequest = 50,
WidthRequest = 50
};
}
}

Просмотреть файл

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.XamlBindingPopup"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
xmlns:models="clr-namespace:CommunityToolkit.Maui.Sample.Models"
Size="{x:Static models:PopupSizeConstants.Large}">
<mct:Popup.BindingContext>
<viewModels:XamlBindingPopupViewModel />
</mct:Popup.BindingContext>
<VerticalStackLayout Style="{StaticResource PopupLayout}">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Start" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, UWP=20, iOS=5, MacCatalyst=5}" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>
<Label Style="{StaticResource Title}"
Text="{Binding Title}" />
<BoxView Style="{StaticResource Divider}" />
<Label Style="{StaticResource Content}"
Text="{Binding Message}" />
</VerticalStackLayout>
</mct:Popup>

Просмотреть файл

@ -0,0 +1,8 @@
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui.Sample;
public partial class XamlBindingPopup : Popup
{
public XamlBindingPopup() => InitializeComponent();
}

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) and '$(MSBuildRuntimeType)' == 'Full'">$(TargetFrameworks);net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) and '$(MSBuildRuntimeType)' == 'Full'">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -80,7 +80,7 @@
<ItemGroup>
<None Include="..\..\build\nuget.png" PackagePath="icon.png" Pack="true" />
<None Include="ReadMe.txt" pack="true" PackagePath="." />
<Using Remove="Microsoft.Maui.Controls"/>
<Using Remove="Microsoft.Maui.Controls" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,124 @@
using CommunityToolkit.Maui.Core.Views;
using Microsoft.Maui.Handlers;
using AView = Android.Views.View;
namespace CommunityToolkit.Maui.Core.Handlers;
public partial class PopupHandler : ElementHandler<IPopup, MauiPopup>
{
internal AView? Container { get; set; }
/// <summary>
/// Action that's triggered when the Popup is closed
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
var popup = handler.NativeView;
if (popup.IsShowing)
{
popup.Dismiss();
}
handler.DisconnectHandler(popup);
}
/// <summary>
/// Action that's triggered when the Popup is Opened.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnOpened(PopupHandler handler, IPopup view, object? result)
{
handler.NativeView?.Show();
}
/// <summary>
/// Action that's triggered when the Popup is dismissed by tapping outside of the popup.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result)
{
if (view.CanBeDismissedByTappingOutsideOfPopup)
{
view.OnDismissedByTappingOutsideOfPopup();
}
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Anchor"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapAnchor(PopupHandler handler, IPopup view)
{
handler.NativeView?.SetAnchor(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view)
{
handler.NativeView?.SetCanBeDismissedByTappingOutsideOfPopup(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Color"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapColor(PopupHandler handler, IPopup view)
{
handler.NativeView?.SetColor(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Size"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapSize(PopupHandler handler, IPopup view)
{
ArgumentNullException.ThrowIfNull(handler.Container);
handler.NativeView?.SetSize(view, handler.Container);
handler.NativeView?.SetAnchor(view);
}
/// <inheritdoc/>
protected override MauiPopup CreateNativeElement()
{
_ = MauiContext ?? throw new InvalidOperationException("MauiContext is null, please check your MauiApplication.");
_ = MauiContext.Context ?? throw new InvalidOperationException("Android Context is null, please check your MauiApplication.");
return new MauiPopup(MauiContext.Context, MauiContext);
}
/// <inheritdoc/>
protected override void ConnectHandler(MauiPopup nativeView)
{
Container = nativeView.SetElement(VirtualView);
}
/// <inheritdoc/>
protected override void DisconnectHandler(MauiPopup nativeView)
{
nativeView.Dispose();
}
void OnShowed(object? sender, EventArgs args)
{
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null");
VirtualView.OnOpened();
}
}

Просмотреть файл

@ -0,0 +1,102 @@
using CommunityToolkit.Maui.Core.Views;
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.Core.Handlers;
public partial class PopupHandler : ElementHandler<IPopup, MauiPopup>
{
/// <summary>
/// Action that's triggered when the Popup is Dismissed.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
handler.DisconnectHandler(handler.NativeView);
}
/// <summary>
/// Action that's triggered when the Popup is Opened.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnOpened(PopupHandler handler, IPopup view, object? result)
{
handler.NativeView.Show();
}
/// <summary>
/// Action that's triggered when the Popup is dismissed by tapping outside of the Popup.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result)
{
view.OnDismissedByTappingOutsideOfPopup();
handler.DisconnectHandler(handler.NativeView);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Anchor"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapAnchor(PopupHandler handler, IPopup view)
{
handler?.NativeView.ConfigureControl();
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view)
{
handler.NativeView.ConfigureControl();
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Color"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapColor(PopupHandler handler, IPopup view)
{
handler.NativeView.SetColor(view);
handler.NativeView.ConfigureControl();
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Size"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapSize(PopupHandler handler, IPopup view)
{
handler.NativeView.ConfigureControl();
}
/// <inheritdoc/>
protected override void DisconnectHandler(MauiPopup nativeView)
{
nativeView.CleanUp();
}
/// <inheritdoc/>
protected override MauiPopup CreateNativeElement()
{
ArgumentNullException.ThrowIfNull(MauiContext);
return new MauiPopup(MauiContext);
}
/// <inheritdoc/>
protected override void ConnectHandler(MauiPopup nativeView)
{
nativeView.SetElement(VirtualView);
base.ConnectHandler(nativeView);
}
}

Просмотреть файл

@ -0,0 +1,106 @@
using CommunityToolkit.Maui.Core.Views;
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.Core.Handlers;
public partial class PopupHandler : ElementHandler<IPopup, MauiPopup>
{
/// <summary>
/// Action that's triggered when the Popup is Dismissed.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static async void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
var vc = handler.NativeView.ViewController;
if (vc is not null)
{
await vc.DismissViewControllerAsync(true);
}
handler.DisconnectHandler(handler.NativeView);
}
/// <summary>
/// Action that's triggered when the Popup is dismissed by tapping outside of the Popup.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result)
{
if (handler.NativeView is not MauiPopup popupRenderer)
{
throw new InvalidOperationException($"{nameof(handler.NativeView)} must be of type {typeof(PopupHandler)}");
}
if (popupRenderer.IsViewLoaded && view.CanBeDismissedByTappingOutsideOfPopup)
{
view.OnDismissedByTappingOutsideOfPopup();
}
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Anchor"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapAnchor(PopupHandler handler, IPopup view)
{
handler.NativeView.SetSize(view);
handler.NativeView.SetLayout(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view)
{
handler.NativeView.SetCanBeDismissedByTappingOutsideOfPopup(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Color"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapColor(PopupHandler handler, IPopup view)
{
handler.NativeView.SetBackgroundColor(view);
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Size"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapSize(PopupHandler handler, IPopup view)
{
handler.NativeView.SetSize(view);
handler.NativeView.SetLayout(view);
}
/// <inheritdoc/>
protected override void ConnectHandler(MauiPopup nativeView)
{
base.ConnectHandler(nativeView);
nativeView.SetElement(VirtualView);
}
/// <inheritdoc/>
protected override MauiPopup CreateNativeElement()
{
return new MauiPopup(MauiContext ?? throw new NullReferenceException(nameof(MauiContext)));
}
/// <inheritdoc/>
protected override void DisconnectHandler(MauiPopup nativeView)
{
base.DisconnectHandler(nativeView);
NativeView.CleanUp();
}
}

Просмотреть файл

@ -0,0 +1,124 @@
namespace CommunityToolkit.Maui.Core.Handlers;
/// <summary>
/// Handler Popup control
/// </summary>
public partial class PopupHandler
{
/// <summary>
/// PropertyMapper for Popup Control
/// </summary>
public static PropertyMapper<IPopup, PopupHandler> PopUpMapper = new(ElementMapper)
{
[nameof(IPopup.Anchor)] = MapAnchor,
[nameof(IPopup.Color)] = MapColor,
[nameof(IPopup.Size)] = MapSize,
[nameof(IPopup.VerticalOptions)] = MapSize,
[nameof(IPopup.HorizontalOptions)] = MapSize,
[nameof(IPopup.CanBeDismissedByTappingOutsideOfPopup)] = MapCanBeDismissedByTappingOutsideOfPopup
};
/// <summary>
/// <see cref ="CommandMapper"/> for Popup Control.
/// </summary>
public static CommandMapper<IPopup, PopupHandler> PopUpCommandMapper = new(ElementCommandMapper)
{
[nameof(IPopup.OnClosed)] = MapOnClosed,
#if !(IOS || MACCATALYST)
[nameof(IPopup.OnOpened)] = MapOnOpened,
#endif
[nameof(IPopup.OnDismissedByTappingOutsideOfPopup)] = MapOnDismissedByTappingOutsideOfPopup
};
/// <summary>
/// Constructor for <see cref="PopupHandler"/>.
/// </summary>
/// <param name="mapper">Custom instance of <see cref="PropertyMapper"/>, if it's null the <see cref="PopUpMapper"/> will be used</param>
/// <param name="commandMapper">Custom instance of <see cref="CommandMapper"/>, if it's null the <see cref="PopUpCommandMapper"/> will be used</param>
public PopupHandler(PropertyMapper? mapper, CommandMapper? commandMapper)
: base(mapper ?? PopUpMapper, commandMapper ?? PopUpCommandMapper)
{
}
/// <summary>
/// Default Constructor for <see cref="PopupHandler"/>.
/// </summary>
public PopupHandler()
: base(PopUpMapper, PopUpCommandMapper)
{
}
}
#if !(IOS || ANDROID || MACCATALYST || WINDOWS)
public partial class PopupHandler : Microsoft.Maui.Handlers.ElementHandler<IPopup, object>
{
/// <inheritdoc/>
protected override object CreateNativeElement() => throw new NotSupportedException();
/// <summary>
/// Action that's triggered when the Popup is closed.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
}
/// <summary>
/// Action that's triggered when the Popup is Opened.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnOpened(PopupHandler handler, IPopup view, object? result)
{
}
/// <summary>
/// Action that's triggered when the Popup is dismissed by tapping outside of the Popup.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result)
{
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Anchor"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapAnchor(PopupHandler handler, IPopup view)
{
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view)
{
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Color"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapColor(PopupHandler handler, IPopup view)
{
}
/// <summary>
/// Action that's triggered when the Popup <see cref="IPopup.Size"/> property changes.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapSize(PopupHandler handler, IPopup view)
{
}
}
#endif

Просмотреть файл

@ -0,0 +1,61 @@
using IElement = Microsoft.Maui.IElement;
using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment;
namespace CommunityToolkit.Maui.Core;
/// <summary>
/// Represents a small View that pops up at front the Page.
/// </summary>
public interface IPopup : IElement
{
/// <summary>
/// Gets the View that Popup will be anchored.
/// </summary>
IView? Anchor { get; }
/// <summary>
/// Gets the Popup's color.
/// </summary>
Color? Color { get; }
/// <summary>
/// Gets the Popup's Content.
/// </summary>
IView? Content { get; }
/// <summary>
/// Gets the horizontal aspect of this element's arrangement in a container.
/// </summary>
LayoutAlignment HorizontalOptions { get; }
/// <summary>
/// Gets the CanBeDismissedByTappingOutsideOfPopup property.
/// </summary>
bool CanBeDismissedByTappingOutsideOfPopup { get; }
/// <summary>
/// Gets the Popup's size.
/// </summary>
Size Size { get; }
/// <summary>
/// Gets the vertical aspect of this element's arrangement in a container.
/// </summary>
LayoutAlignment VerticalOptions { get; }
/// <summary>
/// Occurs when the Popup is closed.
/// </summary>
/// <param name="result">Return value from the Popup.</param>
void OnClosed(object? result = null);
/// <summary>
/// Occurs when the Popup is opened.
/// </summary>
void OnOpened();
/// <summary>
/// Occurs when the Popup is dismissed by a user tapping outside of the Popup.
/// </summary>
void OnDismissedByTappingOutsideOfPopup();
}

Просмотреть файл

@ -0,0 +1,34 @@
namespace CommunityToolkit.Maui.Core;
/// <summary>
/// Popup dismissed event arguments used when a popup is dismissed.
/// </summary>
public class PopupClosedEventArgs
{
/// <summary>
/// Initialization an instance of <see cref="PopupClosedEventArgs"/>.
/// </summary>
/// <param name="result">
/// The result of the popup.
/// </param>
/// <param name="wasDismissedByTappingOutsideOfPopup">
/// If the popup was dismissed by tapping outside of the Popup.
/// </param>
public PopupClosedEventArgs(object? result, bool wasDismissedByTappingOutsideOfPopup)
{
Result = result;
WasDismissedByTappingOutsideOfPopup = wasDismissedByTappingOutsideOfPopup;
}
/// <summary>
/// The resulting object to return.
/// </summary>
public object? Result { get; }
/// <summary>
/// If true, then the user tapped outside the bounds of
/// the popup (a light dismiss). If false, then the
/// popup was dismissed by user action or code.
/// </summary>
public bool WasDismissedByTappingOutsideOfPopup { get; }
}

Просмотреть файл

@ -0,0 +1,12 @@
namespace CommunityToolkit.Maui.Core;
/// <summary>
/// Popup opened event arguments used when a popup is opened.
/// </summary>
public class PopupOpenedEventArgs : EventArgs
{
/// <summary>
/// Empty version of <see cref= "PopupOpenedEventArgs"/>.
/// </summary>
public static new PopupOpenedEventArgs Empty { get; } = new();
}

Просмотреть файл

@ -0,0 +1,104 @@
using System.Diagnostics.CodeAnalysis;
using Android.App;
using Android.Content;
using Microsoft.Maui.Platform;
using AView = Android.Views.View;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// The navite implementation of Popup control.
/// </summary>
public class MauiPopup : Dialog, IDialogInterfaceOnCancelListener
{
readonly IMauiContext mauiContext;
/// <summary>
/// Constructor of <see cref="MauiPopup"/>.
/// </summary>
/// <param name="context">An instance of <see cref="Context"/>.</param>
/// <param name="mauiContext">An instace of <see cref="IMauiContext"/>.</param>
/// <exception cref="ArgumentNullException">If <paramref name="mauiContext"/> is null an exception will be thrown. </exception>
public MauiPopup(Context context, IMauiContext mauiContext)
: base(context)
{
this.mauiContext = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext));
}
/// <summary>
/// An instace of the <see cref="IPopup"/>.
/// </summary>
public IPopup? VirtualView { get; private set; }
/// <summary>
/// Method to initialize the native implementation.
/// </summary>
/// <param name="element">An instance of <see cref="IPopup"/>.</param>
public AView? SetElement(IPopup? element)
{
ArgumentNullException.ThrowIfNull(element);
VirtualView = element;
if (TryCreateContainer(VirtualView, out var container))
{
SubscribeEvents();
}
return container;
}
/// <summary>
/// Method to show the Popup.
/// </summary>
public override void Show()
{
base.Show();
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null");
VirtualView.OnOpened();
}
/// <summary>
/// Method triggered when the Popup is dismissed by tapping outside of the Popup.
/// </summary>
/// <param name="dialog">An instance of the <see cref="IDialogInterface"/>.</param>
public void OnDismissedByTappingOutsideOfPopup(IDialogInterface dialog)
{
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null");
_ = VirtualView.Handler ?? throw new InvalidOperationException($"{nameof(VirtualView.Handler)} cannot be null");
VirtualView.Handler.Invoke(nameof(IPopup.OnDismissedByTappingOutsideOfPopup));
}
/// <summary>
/// Method to CleanUp the resources of the <see cref="MauiPopup"/>.
/// </summary>
public void CleanUp()
{
VirtualView = null;
}
bool TryCreateContainer(in IPopup popup, [NotNullWhen(true)] out AView? container)
{
container = null;
if (popup.Content is null)
{
return false;
}
container = popup.Content.ToNative(mauiContext);
SetContentView(container);
return true;
}
void SubscribeEvents()
{
SetOnCancelListener(this);
}
void IDialogInterfaceOnCancelListener.OnCancel(IDialogInterface? dialog) => OnDismissedByTappingOutsideOfPopup(this);
}

Просмотреть файл

@ -0,0 +1,152 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Handlers;
using UIKit;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// The navite implementation of Popup control.
/// </summary>
public class MauiPopup : UIViewController
{
readonly IMauiContext mauiContext;
/// <summary>
/// Constructor of <see cref="MauiPopup"/>.
/// </summary>
/// <param name="mauiContext">An instace of <see cref="IMauiContext"/>.</param>
/// <exception cref="ArgumentNullException">If <paramref name="mauiContext"/> is null an exception will be thrown. </exception>
public MauiPopup(IMauiContext mauiContext)
{
this.mauiContext = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext));
}
/// <summary>
/// An instance of the <see cref="PageHandler"/> that holds the <see cref="IPopup.Content"/>.
/// </summary>
public PageHandler? Control { get; private set; }
/// <summary>
/// An instace of the <see cref="IPopup"/>.
/// </summary>
public IPopup? VirtualView { get; private set; }
internal UIViewController? ViewController { get; private set; }
/// <summary>
/// Method to update the Popup's size.
/// </summary>
/// <param name="size"></param>
public void SetElementSize(Size size) =>
Control?.ContainerView?.SizeThatFits(size);
/// <inheritdoc/>
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
_ = View ?? throw new InvalidOperationException($"{nameof(View)} cannot be null");
SetElementSize(new Size(View.Bounds.Width, View.Bounds.Height));
}
/// <summary>
/// Method to initialize the native implementation.
/// </summary>
/// <param name="element">An instance of <see cref="IPopup"/>.</param>
[MemberNotNull(nameof(VirtualView), nameof(ViewController))]
public void SetElement(IPopup element)
{
if (element.Parent?.Handler is not PageHandler mainPage)
{
throw new InvalidOperationException($"The {nameof(element.Parent)} must be of type {typeof(PageHandler)}");
}
VirtualView = element;
ModalPresentationStyle = UIModalPresentationStyle.Popover;
_ = View ?? throw new InvalidOperationException($"{nameof(View)} cannot be null");
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null.");
ViewController ??= mainPage.ViewController ?? throw new InvalidOperationException($"{nameof(mainPage.ViewController)} cannot be null"); ;
}
/// <summary>
/// Method to CleanUp the resources of the <see cref="MauiPopup"/>.
/// </summary>
public void CleanUp()
{
if (VirtualView is null)
{
return;
}
VirtualView = null;
if (PresentationController is UIPopoverPresentationController presentationController)
{
presentationController.Delegate = null;
}
}
/// <summary>
///
/// </summary>
/// <param name="func"></param>
/// <param name="virtualView"></param>
/// <returns></returns>
[MemberNotNull(nameof(Control), nameof(ViewController))]
public void CreateControl(Func<IPopup ,PageHandler> func, in IPopup virtualView)
{
Control = func(virtualView);
SetPresentationController();
_ = View ?? throw new InvalidOperationException($"{nameof(View)} cannot be null");
SetView(View, Control);
_ = ViewController ?? throw new InvalidOperationException($"{nameof(ViewController)} cannot be null");
AddToCurrentPageViewController(ViewController);
this.SetLayout(virtualView);
}
void SetView(UIView view, PageHandler control)
{
view.AddSubview(control.ViewController?.View ?? throw new InvalidOperationException($"{nameof(control.ViewController.View)} cannot be null"));
view.Bounds = new(0, 0, PreferredContentSize.Width, PreferredContentSize.Height);
AddChildViewController(control.ViewController);
}
void SetPresentationController()
{
var popOverDelegate = new PopoverDelegate();
popOverDelegate.PopoverDismissedEvent += HandlePopoverDelegateDismissed;
((UIPopoverPresentationController)PresentationController).SourceView = ViewController?.View ?? throw new InvalidOperationException($"{nameof(ViewController.View)} cannot be null");
((UIPopoverPresentationController)PresentationController).Delegate = popOverDelegate;
}
void HandlePopoverDelegateDismissed(object? sender, UIPresentationController e)
{
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null.");
VirtualView.Handler?.Invoke(nameof(IPopup.OnDismissedByTappingOutsideOfPopup));
}
void AddToCurrentPageViewController(UIViewController viewController)
{
viewController.PresentViewController(this, true, null);
}
sealed class PopoverDelegate : UIPopoverPresentationControllerDelegate
{
public event EventHandler<UIPresentationController>? PopoverDismissedEvent;
public override UIModalPresentationStyle GetAdaptivePresentationStyle(UIPresentationController forPresentationController) =>
UIModalPresentationStyle.None;
public override void DidDismiss(UIPresentationController presentationController) =>
PopoverDismissedEvent?.Invoke(this, presentationController);
}
}

Просмотреть файл

@ -0,0 +1,284 @@
using CommunityToolkit.Maui.Core.Handlers;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment;
using WindowsThickness = Microsoft.UI.Xaml.Thickness;
using XamlStyle = Microsoft.UI.Xaml.Style;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// The navite implementation of Popup control.
/// </summary>
public class MauiPopup : Flyout
{
const double defaultBorderThickness = 0;
const double defaultSize = 600;
readonly IMauiContext mauiContext;
/// <summary>
/// Constructor of <see cref="MauiPopup"/>.
/// </summary>
/// <param name="mauiContext">An instace of <see cref="IMauiContext"/>.</param>
/// <exception cref="ArgumentNullException">If <paramref name="mauiContext"/> is null an exception will be thrown. </exception>
public MauiPopup(IMauiContext mauiContext)
{
this.mauiContext = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext));
}
/// <summary>
/// An instace of the <see cref="IPopup"/>.
/// </summary>
public IPopup? VirtualView { get; private set; }
internal Panel? Control { get; set; }
internal XamlStyle FlyoutStyle { get; private set; } = new(typeof(FlyoutPresenter));
Action<Panel>? panelCleanUp;
Func<PopupHandler, Panel?>? createControl;
/// <summary>
/// Method to initialize the native implementation.
/// </summary>
/// <param name="element">An instance of <see cref="IPopup"/>.</param>
public void SetElement(IPopup element)
{
VirtualView = element;
Closing += OnClosing;
}
/// <summary>
/// Method to setup the Content of the Popup using a WrapperControl
/// </summary>
/// <param name="panelCleanUp">Action to be executed when the Handler discconect</param>
/// <param name="createControl">Function to be executed during the create of the popup content</param>
public void SetUpPlatformView(Action<Panel> panelCleanUp, Func<PopupHandler, Panel?> createControl)
{
ArgumentNullException.ThrowIfNull(panelCleanUp);
ArgumentNullException.ThrowIfNull(createControl);
this.panelCleanUp = panelCleanUp;
this.createControl = createControl;
CreateControl();
ConfigureControl();
}
/// <summary>
/// Method to update all the values of the Popup Control.
/// </summary>
public void ConfigureControl()
{
if (VirtualView is null)
{
return;
}
FlyoutStyle = new(typeof(FlyoutPresenter));
SetFlyoutColor();
SetSize();
SetLayout();
ApplyStyles();
}
/// <summary>
/// Method to show the Popup.
/// </summary>
public void Show()
{
if (VirtualView is null)
{
return;
}
ArgumentNullException.ThrowIfNull(VirtualView.Parent);
if (VirtualView.Anchor is not null)
{
var anchor = VirtualView.Anchor.ToNative(mauiContext);
SetAttachedFlyout(anchor, this);
ShowAttachedFlyout(anchor);
}
else
{
var frameworkElement = VirtualView.Parent.ToNative(mauiContext);
frameworkElement.ContextFlyout = this;
SetAttachedFlyout(frameworkElement, this);
ShowAttachedFlyout(frameworkElement);
}
VirtualView.OnOpened();
}
/// <summary>
/// Method to CleanUp the resources of the <see cref="MauiPopup"/>.
/// </summary>
public void CleanUp()
{
Closing -= OnClosing;
Hide();
if (Control is not null)
{
panelCleanUp?.Invoke(Control);
}
VirtualView = null;
Control = null;
}
void CreateControl()
{
if (Control is null && VirtualView?.Content is not null && createControl is not null && VirtualView.Handler is PopupHandler handler)
{
Control = createControl(handler);
Content = Control;
}
}
void SetSize()
{
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null");
if (Control is null)
{
return;
}
var standardSize = new Size { Width = defaultSize, Height = defaultSize / 2 };
var currentSize = VirtualView.Size != default ? VirtualView.Size : standardSize;
if (VirtualView.Content is not null && VirtualView.Size == default)
{
var content = VirtualView.Content;
// There are some situations when the Width and Height values will be NaN
// normally when the dev doesn't set the HeightRequest and WidthRequest
// and we can't use comparasion on those, so the only to prevent the application to crash
// is using this try/catch
try
{
currentSize = new Size(content.Width, content.Height);
}
catch (ArgumentException)
{
}
}
Control.Width = currentSize.Width;
Control.Height = currentSize.Height;
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.MinHeightProperty, currentSize.Height + (defaultBorderThickness * 2)));
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.MinWidthProperty, currentSize.Width + (defaultBorderThickness * 2)));
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.MaxHeightProperty, currentSize.Height + (defaultBorderThickness * 2)));
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.MaxWidthProperty, currentSize.Width + (defaultBorderThickness * 2)));
}
void SetLayout()
{
LightDismissOverlayMode = LightDismissOverlayMode.On;
if (VirtualView is not null)
{
this.SetDialogPosition(VirtualView.VerticalOptions, VirtualView.HorizontalOptions);
}
}
void SetFlyoutColor()
{
_ = VirtualView?.Content ?? throw new NullReferenceException(nameof(IPopup.Content));
var color = VirtualView.Color ?? Colors.Transparent;
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.BackgroundProperty, color.ToWindowsColor()));
if (VirtualView.Color == Colors.Transparent)
{
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.IsDefaultShadowEnabledProperty, false));
}
//Configure border
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.PaddingProperty, 0));
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.BorderThicknessProperty, new WindowsThickness(defaultBorderThickness)));
FlyoutStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(FlyoutPresenter.BorderBrushProperty, Color.FromArgb("#2e6da0").ToWindowsColor()));
}
void ApplyStyles()
{
if(Control is null)
{
return;
}
FlyoutPresenterStyle = FlyoutStyle;
}
void SetDialogPosition(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions)
{
if (IsTopLeft(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.TopEdgeAlignedLeft;
}
else if (IsTop(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.Top;
}
else if (IsTopRight(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.TopEdgeAlignedRight;
}
else if (IsRight(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.Right;
}
else if (IsBottomRight(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.BottomEdgeAlignedRight;
}
else if (IsBottom(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.Bottom;
}
else if (IsBottomLeft(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft;
}
else if (IsLeft(verticalOptions, horizontalOptions))
{
Placement = FlyoutPlacementMode.Left;
}
else if (VirtualView is not null && VirtualView.Anchor is null)
{
Placement = FlyoutPlacementMode.Full;
}
else
{
Placement = FlyoutPlacementMode.Top;
}
static bool IsTopLeft(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.Start && horizontalOptions == LayoutAlignment.Start;
static bool IsTop(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.Start && horizontalOptions == LayoutAlignment.Center;
static bool IsTopRight(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.Start && horizontalOptions == LayoutAlignment.End;
static bool IsRight(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.Center && horizontalOptions == LayoutAlignment.End;
static bool IsBottomRight(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.End && horizontalOptions == LayoutAlignment.End;
static bool IsBottom(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.End && horizontalOptions == LayoutAlignment.Center;
static bool IsBottomLeft(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.End && horizontalOptions == LayoutAlignment.Start;
static bool IsLeft(LayoutAlignment verticalOptions, LayoutAlignment horizontalOptions) => verticalOptions == LayoutAlignment.Center && horizontalOptions == LayoutAlignment.Start;
}
void OnClosing(object? sender, FlyoutBaseClosingEventArgs e)
{
var isLightDismissEnabled = VirtualView?.CanBeDismissedByTappingOutsideOfPopup is true;
if (!isLightDismissEnabled)
{
e.Cancel = true;
}
if (IsOpen && isLightDismissEnabled)
{
VirtualView?.Handler?.Invoke(nameof(IPopup.OnDismissedByTappingOutsideOfPopup));
}
}
}

Просмотреть файл

@ -0,0 +1,215 @@
using Android.App;
using Android.Graphics.Drawables;
using Android.Views;
using Android.Widget;
using CommunityToolkit.Maui.Core;
using Microsoft.Maui.Platform;
using AColorRes = Android.Resource.Color;
using AView = Android.Views.View;
using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// Extension class where Helper methods for Popup lives.
/// </summary>
public static class PopupExtensions
{
/// <summary>
/// Method to update the <see cref="IPopup.Anchor"/> view.
/// </summary>
/// <param name="dialog">An instance of <see cref="Dialog"/>.</param>
/// <param name="popup">An instance of <see cref="IPopup"/>.</param>
/// <exception cref="NullReferenceException">if the <see cref="Android.Views.Window"/> is null an exception will be thrown.</exception>
public static void SetAnchor(this Dialog dialog, in IPopup popup)
{
var window = GetWindow(dialog);
if (popup.Handler is null || popup.Handler.MauiContext is null)
{
return;
}
if (popup.Anchor is not null)
{
var anchorView = popup.Anchor.ToNative(popup.Handler.MauiContext);
var locationOnScreen = new int[2];
anchorView.GetLocationOnScreen(locationOnScreen);
window.SetGravity(GravityFlags.Top | GravityFlags.Left);
window.DecorView.Measure((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
// This logic is tricky, please read these notes if you need to modify
// Android window coordinate starts (0,0) at the top left and (max,max) at the bottom right. All of the positions
// that are being handled in this operation assume the point is at the top left of the rectangle. This means the
// calculation operates in this order:
// 1. Calculate top-left position of Anchor
// 2. Calculate the Actual Center of the Anchor by adding the width /2 and height / 2
// 3. Calculate the top-left point of where the dialog should be positioned by subtracting the Width / 2 and height / 2
// of the dialog that is about to be drawn.
_ = window.Attributes ?? throw new InvalidOperationException($"{nameof(window.Attributes)} cannot be null");
window.Attributes.X = locationOnScreen[0] + (anchorView.Width / 2) - (window.DecorView.MeasuredWidth / 2);
window.Attributes.Y = locationOnScreen[1] + (anchorView.Height / 2) - (window.DecorView.MeasuredHeight / 2);
}
else
{
SetDialogPosition(popup, window);
}
}
/// <summary>
/// Method to update the <see cref="IPopup.Color"/> property.
/// </summary>
/// <param name="dialog">An instance of <see cref="Dialog"/>.</param>
/// <param name="popup">An instance of <see cref="IPopup"/>.</param>
public static void SetColor(this Dialog dialog, in IPopup popup)
{
if (popup.Color is null)
{
return;
}
var window = GetWindow(dialog);
window.SetBackgroundDrawable(new ColorDrawable(popup.Color.ToNative(AColorRes.BackgroundLight, dialog.Context)));
}
/// <summary>
/// Method to update the <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property.
/// </summary>
/// <param name="dialog">An instance of <see cref="Dialog"/>.</param>
/// <param name="popup">An instance of <see cref="IPopup"/>.</param>
public static void SetCanBeDismissedByTappingOutsideOfPopup(this Dialog dialog, in IPopup popup)
{
if (popup.CanBeDismissedByTappingOutsideOfPopup)
{
return;
}
dialog.SetCancelable(false);
dialog.SetCanceledOnTouchOutside(false);
}
/// <summary>
/// Method to update the <see cref="IPopup.Size"/> property.
/// </summary>
/// <param name="dialog">An instance of <see cref="Dialog"/>.</param>
/// <param name="popup">An instance of <see cref="IPopup"/>.</param>
/// <param name="container">The native representation of <see cref="IPopup.Content"/>.</param>
/// <exception cref="NullReferenceException">if the <see cref="Android.Views.Window"/> is null an exception will be thrown. If the <paramref name="container"/> is null an exception will be thrown.</exception>
public static void SetSize(this Dialog dialog, in IPopup popup, in AView container)
{
ArgumentNullException.ThrowIfNull(popup.Content);
int horizontalParams, verticalParams;
var window = GetWindow(dialog);
var context = dialog.Context;
var decorView = (ViewGroup)window.DecorView;
var child = decorView.GetChildAt(0) ?? throw new NullReferenceException();
var realWidth = (int)context.ToPixels(popup.Size.Width);
var realHeight = (int)context.ToPixels(popup.Size.Height);
var realContentWidth = (int)context.ToPixels(popup.Content.Width);
var realContentHeight = (int)context.ToPixels(popup.Content.Height);
realWidth = realWidth is 0 ? realContentWidth : realWidth;
realHeight = realHeight is 0 ? realContentHeight : realHeight;
if (realHeight is 0 || realWidth is 0)
{
realWidth = (int?)(context.Resources?.DisplayMetrics?.WidthPixels * 0.8) ?? throw new NullReferenceException();
realHeight = (int?)(context.Resources?.DisplayMetrics?.HeightPixels * 0.6) ?? throw new NullReferenceException();
}
var childLayoutParams = (FrameLayout.LayoutParams)(child.LayoutParameters ?? throw new NullReferenceException());
childLayoutParams.Width = realWidth;
childLayoutParams.Height = realHeight;
child.LayoutParameters = childLayoutParams;
if (realContentWidth > -1)
{
var inputMeasuredWidth = realContentWidth > realWidth ? realWidth : realContentWidth;
container.Measure(inputMeasuredWidth, (int)MeasureSpecMode.Unspecified);
horizontalParams = container.MeasuredWidth;
}
else
{
container.Measure(realWidth, (int)MeasureSpecMode.Unspecified);
horizontalParams = container.MeasuredWidth > realWidth ? realWidth : container.MeasuredWidth;
}
if (realContentHeight > -1)
{
verticalParams = realContentHeight;
}
else
{
var inputMeasuredWidth = realContentWidth > -1 ? horizontalParams : realWidth;
container.Measure(inputMeasuredWidth, (int)MeasureSpecMode.Unspecified);
verticalParams = container.MeasuredHeight > realHeight ? realHeight : container.MeasuredHeight;
}
var containerLayoutParams = new FrameLayout.LayoutParams(horizontalParams, verticalParams);
switch (popup.Content.VerticalLayoutAlignment)
{
case LayoutAlignment.Start:
containerLayoutParams.Gravity = GravityFlags.Top;
break;
case LayoutAlignment.Center:
case LayoutAlignment.Fill:
containerLayoutParams.Gravity = GravityFlags.FillVertical;
containerLayoutParams.Height = realHeight;
break;
case LayoutAlignment.End:
containerLayoutParams.Gravity = GravityFlags.Bottom;
break;
default:
throw new NotSupportedException();
}
switch (popup.Content.HorizontalLayoutAlignment)
{
case LayoutAlignment.Start:
containerLayoutParams.Gravity |= GravityFlags.Left;
break;
case LayoutAlignment.Center:
case LayoutAlignment.Fill:
containerLayoutParams.Gravity |= GravityFlags.FillHorizontal;
containerLayoutParams.Width = realWidth;
break;
case LayoutAlignment.End:
containerLayoutParams.Gravity |= GravityFlags.Right;
break;
default:
throw new NotSupportedException();
}
container.LayoutParameters = containerLayoutParams;
}
static void SetDialogPosition(in IPopup popup, Android.Views.Window window)
{
var gravityFlags = popup.VerticalOptions switch
{
LayoutAlignment.Start => GravityFlags.Top,
LayoutAlignment.End => GravityFlags.Bottom,
_ => GravityFlags.CenterVertical,
};
gravityFlags |= popup.HorizontalOptions switch
{
LayoutAlignment.Start => GravityFlags.Left,
LayoutAlignment.End => GravityFlags.Right,
_ => GravityFlags.CenterHorizontal,
};
window.SetGravity(gravityFlags);
}
static Android.Views.Window GetWindow(in Dialog dialog) =>
dialog.Window ?? throw new NullReferenceException("Android.Views.Window is null");
}

Просмотреть файл

@ -0,0 +1,103 @@
using CommunityToolkit.Maui.Core;
using CoreGraphics;
using Microsoft.Maui.Platform;
using UIKit;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// Extension class where Helper methods for Popup lives.
/// </summary>
public static class PopupExtensions
{
/// <summary>
/// Method to update the <see cref="IPopup.Size"/> of the Popup.
/// </summary>
/// <param name="mauiPopup">An instance of <see cref="MauiPopup"/>.</param>
/// <param name="popup">An istance of <see cref="IPopup"/>.</param>
public static void SetSize(this MauiPopup mauiPopup, in IPopup popup)
{
if (!popup.Size.IsZero)
{
mauiPopup.PreferredContentSize = new CGSize(popup.Size.Width, popup.Size.Height);
}
else if (popup.Content is not null)
{
if (!popup.Content.DesiredSize.IsZero)
{
var contentSize = popup.Content.DesiredSize;
mauiPopup.PreferredContentSize = new CGSize(contentSize.Width, contentSize.Height);
}
else
{
var measure = popup.Content.Measure(double.PositiveInfinity, double.PositiveInfinity);
mauiPopup.PreferredContentSize = new CGSize(measure.Width, measure.Height);
}
}
}
/// <summary>
/// Method to update the <see cref="IPopup.Color"/> of the Popup.
/// </summary>
/// <param name="mauiPopup">An instance of <see cref="MauiPopup"/>.</param>
/// <param name="popup">An istance of <see cref="IPopup"/>.</param>
public static void SetBackgroundColor(this MauiPopup mauiPopup, in IPopup popup)
{
if (mauiPopup.Control is null)
{
return;
}
var color = popup.Color?.ToNative();
mauiPopup.Control.NativeView.BackgroundColor = color;
}
/// <summary>
/// Method to update the <see cref="IPopup.CanBeDismissedByTappingOutsideOfPopup"/> property of the Popup.
/// </summary>
/// <param name="mauiPopup">An instance of <see cref="MauiPopup"/>.</param>
/// <param name="popup">An istance of <see cref="IPopup"/>.</param>
public static void SetCanBeDismissedByTappingOutsideOfPopup(this MauiPopup mauiPopup, in IPopup popup)
{
mauiPopup.ModalInPresentation = !popup.CanBeDismissedByTappingOutsideOfPopup;
}
/// <summary>
/// Method to update the layout of the Popup and <see cref="IPopup.Content"/>.
/// </summary>
/// <param name="mauiPopup">An instance of <see cref="MauiPopup"/>.</param>
/// <param name="popup">An istance of <see cref="IPopup"/>.</param>
public static void SetLayout(this MauiPopup mauiPopup, in IPopup popup)
{
var presentationController = mauiPopup.PresentationController;
var preferredContentSize = mauiPopup.PreferredContentSize;
((UIPopoverPresentationController)presentationController).SourceRect = new CGRect(0, 0, preferredContentSize.Width, preferredContentSize.Height);
if (popup.Anchor is null)
{
var originY = popup.VerticalOptions switch
{
Microsoft.Maui.Primitives.LayoutAlignment.End => UIScreen.MainScreen.Bounds.Height,
Microsoft.Maui.Primitives.LayoutAlignment.Center => UIScreen.MainScreen.Bounds.Height / 2,
_ => 0f
};
var originX = popup.HorizontalOptions switch
{
Microsoft.Maui.Primitives.LayoutAlignment.End => UIScreen.MainScreen.Bounds.Width,
Microsoft.Maui.Primitives.LayoutAlignment.Center => UIScreen.MainScreen.Bounds.Width / 2,
_ => 0f
};
mauiPopup.PopoverPresentationController.SourceRect = new CGRect(originX, originY, 0, 0);
mauiPopup.PopoverPresentationController.PermittedArrowDirections = 0;
}
else
{
var view = popup.Anchor.ToNative(popup.Handler?.MauiContext ?? throw new NullReferenceException());
mauiPopup.PopoverPresentationController.SourceView = view;
mauiPopup.PopoverPresentationController.SourceRect = view.Bounds;
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
using Microsoft.Maui.Platform;
namespace CommunityToolkit.Maui.Core.Views;
/// <summary>
/// Extension class where Helper methods for Popup lives.
/// </summary>
public static class PopupExtensions
{
/// <summary>
/// Method to update the <see cref="Maui.Core.IPopup.Content"/> based on the <see cref="Maui.Core.IPopup.Color"/>.
/// </summary>
/// <param name="flyout">An instance of <see cref="MauiPopup"/>.</param>
/// <param name="popup">An instance of <see cref="Maui.Core.IPopup"/>.</param>
public static void SetColor(this MauiPopup flyout, IPopup popup)
{
ArgumentNullException.ThrowIfNull(popup.Content);
var color = popup.Color ?? Colors.Transparent;
var view = popup.Content;
if (view.Background is null && flyout.Control is not null)
{
flyout.Control.Background = color.ToNative();
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
using CommunityToolkit.Maui.UnitTests.Mocks;
namespace CommunityToolkit.Maui.UnitTests;
public abstract class BaseHandlerTest : BaseTest
{
protected BaseHandlerTest()
{
CreateAndSetMockApplication();
}
protected static TElementHandler CreateElementHandler<TElementHandler>(Microsoft.Maui.IElement view, bool hasMauiContext = true)
where TElementHandler : IElementHandler, new()
{
var mockElementHandler = new TElementHandler();
mockElementHandler.SetVirtualView(view);
if (hasMauiContext)
{
mockElementHandler.SetMauiContext(Application.Current?.Handler.MauiContext ?? throw new NullReferenceException());
}
return mockElementHandler;
}
protected static TViewHandler CreateViewHandler<TViewHandler>(IView view, bool hasMauiContext = true)
where TViewHandler : IViewHandler, new()
{
var mockViewHandler = new TViewHandler();
mockViewHandler.SetVirtualView(view);
if (hasMauiContext)
{
mockViewHandler.SetMauiContext(Application.Current?.Handler.MauiContext ?? throw new NullReferenceException());
}
return mockViewHandler;
}
static void CreateAndSetMockApplication()
{
var appBuilder = MauiApp.CreateBuilder()
.UseMauiApp<MockApplication>();
var mauiApp = appBuilder.Build();
var application = mauiApp.Services.GetRequiredService<IApplication>();
application.Handler = new ApplicationHandlerStub();
application.Handler.SetMauiContext(new HandlersContextStub(mauiApp.Services));
}
}

Просмотреть файл

@ -1,7 +1,7 @@
using CommunityToolkit.Maui.Layouts;
using Xunit;
namespace CommunityToolkit.Maui.UnitTests.Views;
namespace CommunityToolkit.Maui.UnitTests.Layouts;
public class UniformItemsLayoutTests : BaseTest
{

Просмотреть файл

@ -0,0 +1,19 @@
using Microsoft.Maui.Animations;
namespace CommunityToolkit.Maui.UnitTests.Mocks;
class HandlersContextStub : IMauiContext
{
public HandlersContextStub(IServiceProvider services)
{
Services = services;
Handlers = Services.GetRequiredService<IMauiHandlersFactory>();
AnimationManager = services.GetService<IAnimationManager>() ?? throw new NullReferenceException();
}
public IServiceProvider Services { get; }
public IMauiHandlersFactory Handlers { get; }
public IAnimationManager AnimationManager { get; }
}

Просмотреть файл

@ -1,6 +1,23 @@
namespace CommunityToolkit.Maui.UnitTests.Mocks;
using Microsoft.Maui.Handlers;
public class MockApplication : Application
namespace CommunityToolkit.Maui.UnitTests.Mocks;
class MockApplication : Application
{
public new Application? Current = null;
}
// Inspired by https://github.com/dotnet/maui/blob/main/src/Controls/tests/Core.UnitTests/TestClasses/ApplicationHandlerStub.cs
class ApplicationHandlerStub : ElementHandler<IApplication, object>
{
public ApplicationHandlerStub() : base(Mapper)
{
}
public static IPropertyMapper<IApplication, ApplicationHandlerStub> Mapper = new PropertyMapper<IApplication, ApplicationHandlerStub>(ElementMapper);
protected override object CreateNativeElement()
{
return new object();
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.UnitTests.Mocks;
public class MockPageHandler : ViewHandler<IContentView, object>
{
public MockPageHandler() : base(new PropertyMapper<IView>())
{
}
public MockPageHandler(IPropertyMapper mapper) : base(mapper)
{
}
protected override object CreateNativeView()
{
return new object();
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using CommunityToolkit.Maui.Core;
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.UnitTests.Mocks;
public class MockPopupHandler : ElementHandler<IPopup, object>
{
public static CommandMapper<IPopup, MockPopupHandler> PopUpCommandMapper = new(ElementCommandMapper)
{
[nameof(IPopup.OnOpened)] = MapOnOpened
};
public MockPopupHandler() : base(new PropertyMapper<IView>(), PopUpCommandMapper)
{
}
public MockPopupHandler(IPropertyMapper mapper) : base(mapper, PopUpCommandMapper)
{
}
public int OnOpenedCount { get; private set; }
protected override object CreateNativeElement()
{
return new object();
}
static void MapOnOpened(MockPopupHandler arg1, IPopup arg2, object? arg3)
{
arg1.OnOpenedCount++;
}
}

Просмотреть файл

@ -0,0 +1,196 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Extensions;
using CommunityToolkit.Maui.UnitTests.Mocks;
using CommunityToolkit.Maui.Views;
using Xunit;
namespace CommunityToolkit.Maui.UnitTests.Views;
public class PopupTests : BaseHandlerTest
{
const string resultWhenUserTapsOutsideOfPopup = "User Tapped Outside of Popup";
readonly IPopup popup = new MockPopup();
public PopupTests()
{
Assert.IsAssignableFrom<IPopup>(new MockPopup());
}
[Fact]
public void GetRequiredServiceThrowsOnNoContext()
{
var handlerStub = new MockPopupHandler();
Assert.Null((handlerStub as IElementHandler).MauiContext);
var ex = Assert.Throws<InvalidOperationException>(() => handlerStub.GetRequiredService<IFooService>());
Assert.Contains("the context", ex.Message);
Assert.Contains("MauiContext", ex.Message);
}
[Fact]
public async Task OnOnpenedMapperIsCalled()
{
var app = Application.Current ?? throw new NullReferenceException();
var page = new ContentPage
{
Content = new Label
{
Text = "Hello there"
}
};
// Make sure that our page will have a Handler
CreateViewHandler<MockPageHandler>(page);
app.MainPage = page;
var popupHandler = CreateElementHandler<MockPopupHandler>(popup);
Assert.NotNull(popup.Handler);
Assert.NotNull(page.Handler);
page.ShowPopup((MockPopup)popup);
Assert.Equal(1, popupHandler.OnOpenedCount);
popup.OnDismissedByTappingOutsideOfPopup();
var popupTask = page.ShowPopupAsync((MockPopup)popup);
popup.OnDismissedByTappingOutsideOfPopup();
await popupTask;
Assert.Equal(2, popupHandler.OnOpenedCount);
}
[Fact]
public void PopupDismissedByTappingOutsideOfPopup()
{
string? dismissedByTappingOutsideOfPopupResult = null;
var isPopupDismissedByTappingOutsideOfPopup = false;
var app = Application.Current ?? throw new NullReferenceException();
var page = new ContentPage
{
Content = new Label
{
Text = "Hello there"
}
};
((MockPopup)popup).Closed += (s, e) =>
{
Assert.Equal(popup, s);
isPopupDismissedByTappingOutsideOfPopup = e.WasDismissedByTappingOutsideOfPopup;
dismissedByTappingOutsideOfPopupResult = (string?)e.Result;
};
// Make sure that our page will have a Handler
CreateViewHandler<MockPageHandler>(page);
app.MainPage = page;
CreateElementHandler<MockPopupHandler>(popup);
Assert.NotNull(popup.Handler);
Assert.NotNull(page.Handler);
popup.OnDismissedByTappingOutsideOfPopup();
Assert.True(isPopupDismissedByTappingOutsideOfPopup);
Assert.Equal(resultWhenUserTapsOutsideOfPopup, dismissedByTappingOutsideOfPopupResult);
}
[Fact]
public void OnDismissedWithResult()
{
object? result = null;
var isPopupDismissed = false;
var app = Application.Current ?? throw new NullReferenceException();
var page = new ContentPage
{
Content = new Label
{
Text = "Hello there"
}
};
// Make sure that our page will have a Handler
CreateViewHandler<MockPageHandler>(page);
app.MainPage = page;
// Make sure that our popup will have a Handler
CreateElementHandler<MockPopupHandler>(popup);
Assert.NotNull(popup.Handler);
Assert.NotNull(page.Handler);
((MockPopup)popup).Closed += (_, e) =>
{
result = e.Result;
isPopupDismissed = true;
};
((MockPopup)popup).Close(new object());
Assert.True(isPopupDismissed);
Assert.NotNull(result);
}
[Fact]
public void OnDismissedWithoutResult()
{
object? result = null;
var isPopupDismissed = false;
var app = Application.Current ?? throw new NullReferenceException();
var page = new ContentPage
{
Content = new Label
{
Text = "Hello there"
}
};
// Make sure that our page will have a Handler
CreateViewHandler<MockPageHandler>(page);
app.MainPage = page;
// Make sure that our popup will have a Handler
CreateElementHandler<MockPopupHandler>(popup);
Assert.NotNull(popup.Handler);
Assert.NotNull(page.Handler);
((MockPopup)popup).Closed += (_, e) =>
{
result = e.Result;
isPopupDismissed = true;
};
((MockPopup)popup).Close();
Assert.True(isPopupDismissed);
Assert.Null(result);
}
class MockPopup : Popup
{
public MockPopup()
{
ResultWhenUserTapsOutsideOfPopup = resultWhenUserTapsOutsideOfPopup;
}
}
interface IFooService
{
public int MyProperty { get; set; }
}
}

Просмотреть файл

@ -1,4 +1,6 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Core.Handlers;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
namespace CommunityToolkit.Maui;
@ -14,6 +16,11 @@ public static class AppBuilderExtensions
/// <returns><see cref="MauiAppBuilder"/> initialized for <see cref="CommunityToolkit.Maui"/></returns>
public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder)
{
builder.ConfigureMauiHandlers(h =>
{
h.AddHandler(typeof(Popup), typeof(PopupHandler));
});
Popup.RemapForControls();
return builder.UseMauiCommunityToolkitCore();
}
}

Просмотреть файл

@ -1,6 +1,8 @@
[assembly: XmlnsDefinition(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(CommunityToolkit.Maui.Alerts))]
[assembly: XmlnsDefinition(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(CommunityToolkit.Maui.Behaviors))]
[assembly: XmlnsDefinition(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(CommunityToolkit.Maui.Converters))]
[assembly: XmlnsDefinition(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(CommunityToolkit.Maui.Views))]
[assembly: XmlnsDefinition(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(CommunityToolkit.Maui.Layouts))]
[assembly: Microsoft.Maui.Controls.XmlnsPrefix(Constants.XamlNamespace, "toolkit")]

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) and '$(MSBuildRuntimeType)' == 'Full'">$(TargetFrameworks);net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) and '$(MSBuildRuntimeType)' == 'Full'">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -78,7 +78,6 @@
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Maui.Core\CommunityToolkit.Maui.Core.csproj" />
</ItemGroup>

Просмотреть файл

@ -0,0 +1,33 @@
using CommunityToolkit.Maui.Core.Handlers;
using CommunityToolkit.Maui.Core;
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.Views;
public partial class Popup
{
/// <summary>
/// Action that's triggered when the Popup is Opened.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnOpened(PopupHandler handler, IPopup view, object? result)
{
handler.NativeView?.CreateControl(CreatePageHandler, view);
view.OnOpened();
static PageHandler CreatePageHandler(IPopup virtualView)
{
var mauiContext = virtualView.Handler?.MauiContext ?? throw new NullReferenceException(nameof(IMauiContext));
var view = (View?)virtualView.Content ?? throw new InvalidOperationException($"{nameof(IPopup.Content)} can't be null here.");
var contentPage = new ContentPage { Content = view };
contentPage.Parent = Application.Current?.MainPage;
contentPage.SetBinding(VisualElement.BindingContextProperty, new Binding { Source = virtualView, Path = VisualElement.BindingContextProperty.PropertyName });
return (PageHandler)contentPage.ToHandler(mauiContext);
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using CommunityToolkit.Maui.Core.Handlers;
using CommunityToolkit.Maui.Core;
namespace CommunityToolkit.Maui.Views;
public partial class Popup
{
/// <summary>
///
/// </summary>
public static CommandMapper<IPopup, PopupHandler> ControlPopUpCommandMapper = new(PopupHandler.PopUpCommandMapper)
{
#if IOS || MACCATALYST
[nameof(IPopup.OnOpened)] = MapOnOpened
#endif
};
internal static void RemapForControls()
{
PopupHandler.PopUpCommandMapper = ControlPopUpCommandMapper;
}
}

Просмотреть файл

@ -0,0 +1,33 @@
using CommunityToolkit.Maui.Core.Handlers;
using CommunityToolkit.Maui.Core.Views;
using Microsoft.UI.Xaml.Controls;
namespace CommunityToolkit.Maui.Views;
public partial class Popup
{
void OnPopupHandlerChanged(object? sender, EventArgs e)
{
if (Handler is null || Handler.NativeView is null)
{
return;
}
((MauiPopup)Handler.NativeView).SetUpPlatformView(CleanUp, CreateWrapperContent);
static void CleanUp(Panel wrapper)
{
((WrapperControl)wrapper).CleanUp();
}
static Panel? CreateWrapperContent(PopupHandler handler)
{
if (handler.VirtualView.Content is null || handler.MauiContext is null)
{
return null;
}
return new WrapperControl((View)handler.VirtualView.Content, handler.MauiContext);
}
}
}

Просмотреть файл

@ -0,0 +1,248 @@
using CommunityToolkit.Maui.Core;
using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment;
namespace CommunityToolkit.Maui.Views;
/// <summary>
/// Represents a small View that pops up at front the Page. Implements <see cref="IPopup"/>.
/// </summary>
[ContentProperty(nameof(Content))]
public partial class Popup : Element, IPopup
{
/// <summary>
/// Backing BindableProperty for the <see cref="Content"/> property.
/// </summary>
public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(Popup), propertyChanged: OnContentChanged);
/// <summary>
/// Backing BindableProperty for the <see cref="Color"/> property.
/// </summary>
public static readonly BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(Popup), Colors.LightGray);
/// <summary>
/// Backing BindableProperty for the <see cref="Size"/> property.
/// </summary>
public static readonly BindableProperty SizeProperty = BindableProperty.Create(nameof(Size), typeof(Size), typeof(Popup), default(Size));
/// <summary>
/// Backing BindableProperty for the <see cref="CanBeDismissedByTappingOutsideOfPopup"/> property.
/// </summary>
public static readonly BindableProperty CanBeDismissedByTappingOutsideOfPopupProperty = BindableProperty.Create(nameof(CanBeDismissedByTappingOutsideOfPopup), typeof(bool), typeof(Popup), true);
/// <summary>
/// Backing BindableProperty for the <see cref="VerticalOptions"/> property.
/// </summary>
public static readonly BindableProperty VerticalOptionsProperty = BindableProperty.Create(nameof(VerticalOptions), typeof(LayoutAlignment), typeof(Popup), LayoutAlignment.Center);
/// <summary>
/// Backing BindableProperty for the <see cref="HorizontalOptions"/> property.
/// </summary>
public static readonly BindableProperty HorizontalOptionsProperty = BindableProperty.Create(nameof(HorizontalOptions), typeof(LayoutAlignment), typeof(Popup), LayoutAlignment.Center);
readonly WeakEventManager dismissWeakEventManager = new();
readonly WeakEventManager openedWeakEventManager = new();
readonly Lazy<PlatformConfigurationRegistry<Popup>> platformConfigurationRegistry;
TaskCompletionSource<object?> taskCompletionSource = new();
/// <summary>
/// Instantiates a new instance of <see cref="Popup"/>.
/// </summary>
public Popup()
{
platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Popup>>(() => new(this));
VerticalOptions = HorizontalOptions = LayoutAlignment.Center;
#if WINDOWS
this.HandlerChanged += OnPopupHandlerChanged;
#endif
}
/// <summary>
/// Dismissed event is invoked when the popup is closed.
/// </summary>
public event EventHandler<PopupClosedEventArgs> Closed
{
add => dismissWeakEventManager.AddEventHandler(value);
remove => dismissWeakEventManager.RemoveEventHandler(value);
}
/// <summary>
/// Opened event is invoked when the popup is opened.
/// </summary>
public event EventHandler<PopupOpenedEventArgs> Opened
{
add => openedWeakEventManager.AddEventHandler(value);
remove => openedWeakEventManager.RemoveEventHandler(value);
}
/// <summary>
/// Gets the final result of the dismissed popup.
/// </summary>
public Task<object?> Result => taskCompletionSource.Task;
/// <summary>
/// Gets or sets the <see cref="View"/> content to render in the Popup.
/// </summary>
/// <remarks>
/// The View can be or type: <see cref="View"/>, <see cref="ContentPage"/> or <see cref="NavigationPage"/>
/// </remarks>
public virtual View? Content
{
get => (View?)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="Color"/> of the Popup.
/// </summary>
/// <remarks>
/// This color sets the native background color of the <see cref="Popup"/>, which is
/// independent of any background color configured in the actual View.
/// </remarks>
public Color Color
{
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="LayoutOptions"/> for positioning the <see cref="Popup"/> vertically on the screen.
/// </summary>
public LayoutAlignment VerticalOptions
{
get => (LayoutAlignment)GetValue(VerticalOptionsProperty);
set => SetValue(VerticalOptionsProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="LayoutOptions"/> for positioning the <see cref="Popup"/> horizontally on the screen.
/// </summary>
public LayoutAlignment HorizontalOptions
{
get => (LayoutAlignment)GetValue(HorizontalOptionsProperty);
set => SetValue(HorizontalOptionsProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="Size"/> of the Popup Display.
/// </summary>
/// <remarks>
/// The Popup will always try to constrain the actual size of the <see cref="Popup" />
/// to the <see cref="Popup" /> of the View unless a <see cref="Size"/> is specified.
/// If the <see cref="Popup" /> contiains <see cref="LayoutOptions"/> a <see cref="Size"/>
/// will be required. This will allow the View to have a concept of <see cref="Size"/>
/// that varies from the actual <see cref="Size"/> of the <see cref="Popup" />
/// </remarks>
public Size Size
{
get => (Size)GetValue(SizeProperty);
set => SetValue(SizeProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the popup can be dismissed by tapping outside of the Popup.
/// </summary>
/// <remarks>
/// When true and the user taps outside of the popup it will dismiss.
/// On Android - when false the hardware back button is disabled.
/// </remarks>
public bool CanBeDismissedByTappingOutsideOfPopup
{
get => (bool)GetValue(CanBeDismissedByTappingOutsideOfPopupProperty);
set => SetValue(CanBeDismissedByTappingOutsideOfPopupProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="View"/> anchor.
/// </summary>
/// <remarks>
/// The Anchor is where the Popup will render closest to. When an Anchor is configured
/// the popup will appear centered over that control or as close as possible.
/// </remarks>
public View? Anchor { get; set; }
/// <summary>
/// Gets or sets the result that will return when user taps outside of the Popup.
/// </summary>
protected object? ResultWhenUserTapsOutsideOfPopup { get; set; }
/// <inheritdoc/>
IView? IPopup.Anchor => Anchor;
/// <inheritdoc/>
IView? IPopup.Content => Content;
/// <summary>
/// Resets the Popup.
/// </summary>
public void Reset() => taskCompletionSource = new();
/// <summary>
/// Close the current popup.
/// </summary>
/// <param name="result">
/// The result to return.
/// </param>
public void Close(object? result = null)
{
taskCompletionSource.TrySetResult(result);
OnClosed(result, false);
}
/// <summary>
/// Invokes the <see cref="Opened"/> event.
/// </summary>
internal virtual void OnOpened() =>
openedWeakEventManager.HandleEvent(this, PopupOpenedEventArgs.Empty, nameof(Opened));
/// <summary>
/// Invokes the <see cref="Closed"/> event.
/// </summary>
/// <param name="result">
/// Sets the <see cref="PopupClosedEventArgs"/> Property of <see cref="PopupClosedEventArgs.Result"/>.
/// </param>
/// /// <param name="wasDismissedByTappingOutsideOfPopup">
/// Sets the <see cref="PopupClosedEventArgs"/> Property of <see cref="PopupClosedEventArgs.WasDismissedByTappingOutsideOfPopup"/>/>.
/// </param>
protected void OnClosed(object? result, bool wasDismissedByTappingOutsideOfPopup)
{
((IPopup)this).OnClosed(result);
dismissWeakEventManager.HandleEvent(this, new PopupClosedEventArgs(result, wasDismissedByTappingOutsideOfPopup), nameof(Closed));
}
/// <summary>
/// Invoked when the popup is dismissed by tapping outside of the popup.
/// </summary>
protected internal virtual void OnDismissedByTappingOutsideOfPopup()
{
taskCompletionSource.TrySetResult(ResultWhenUserTapsOutsideOfPopup);
OnClosed(ResultWhenUserTapsOutsideOfPopup, true);
}
/// <summary>
///<inheritdoc/>
/// </summary>
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (Content is not null)
{
SetInheritedBindingContext(Content, BindingContext);
}
}
static void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var popup = (Popup)bindable;
popup.OnBindingContextChanged();
}
void IPopup.OnClosed(object? result) => Handler.Invoke(nameof(IPopup.OnClosed), result);
void IPopup.OnOpened() => OnOpened();
void IPopup.OnDismissedByTappingOutsideOfPopup() => OnDismissedByTappingOutsideOfPopup();
}

Просмотреть файл

@ -0,0 +1,83 @@
using System.Runtime.CompilerServices;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Platform;
namespace CommunityToolkit.Maui.Views;
/// <summary>
/// Extension methods for <see cref="Popup"/>.
/// </summary>
public static partial class PopupExtensions
{
/// <summary>
/// Displays a popup.
/// </summary>
/// <param name="page">
/// The current <see cref="Page"/>.
/// </param>
/// <param name="popup">
/// The <see cref="Popup"/> to display.
/// </param>
public static void ShowPopup<TPopup>(this Page page, TPopup popup) where TPopup : Popup
{
#if WINDOWS
PlatformShowPopup(popup, GetMauiContext(page));
#else
CreatePopup(page, popup);
#endif
}
/// <summary>
/// Displays a popup and returns a result.
/// </summary>
/// <param name="page">
/// The current <see cref="Page"/>.
/// </param>
/// <param name="popup">
/// The <see cref="Popup"/> to display.
/// </param>
/// <returns>
/// A task that will complete once the <see cref="Popup"/> is dismissed.
/// </returns>
public static Task<object?> ShowPopupAsync<TPopup>(this Page page, TPopup popup) where TPopup : Popup
{
#if WINDOWS
return PlatformShowPopupAsync(popup, GetMauiContext(page));
#else
CreatePopup(page, popup);
return popup.Result;
#endif
}
static void CreatePopup(Page page, Popup popup)
{
var mauiContext = GetMauiContext(page);
popup.Parent = PageExtensions.GetCurrentPage(page);
var popupNative = popup.ToHandler(mauiContext);
popupNative.Invoke(nameof(IPopup.OnOpened));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static IMauiContext GetMauiContext(Page page)
{
return page.Handler?.MauiContext ?? throw new InvalidOperationException("Could not locate MauiContext");
}
}
#if !(ANDROID || IOS || MACCATALYST || WINDOWS)
/// <summary>
/// Extension methods for <see cref="Popup"/>.
/// </summary>
public static partial class PopupExtensions
{
static void PlatformShowPopup(Popup popup, IMauiContext mauiContext) =>
throw new NotSupportedException($"The current platform '{Device.RuntimePlatform}' does not support CommunityToolkit.Maui.Core.Popup");
static Task<object?> PlatformShowPopupAsync(Popup popup, IMauiContext mauiContext) =>
throw new NotSupportedException($"The current platform '{Device.RuntimePlatform}' does not support CommunityToolkit.Maui.Core.Popup.");
}
#endif

Просмотреть файл

@ -0,0 +1,27 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Platform;
namespace CommunityToolkit.Maui.Views;
/// <summary>
/// Extension methods for <see cref="Popup"/>.
/// </summary>
public static partial class PopupExtensions
{
static void PlatformShowPopup(Popup popup, IMauiContext mauiContext)
{
var window = mauiContext.GetNativeWindow().GetWindow() ?? throw new NullReferenceException("Window is null.");
popup.Parent = PageExtensions.GetCurrentPage((Page)window.Content);
var native = popup.ToHandler(mauiContext);
native?.Invoke(nameof(IPopup.OnOpened));
}
static Task<object?> PlatformShowPopupAsync(Popup popup, IMauiContext mauiContext)
{
PlatformShowPopup(popup, mauiContext);
return popup.Result;
}
}

Просмотреть файл

@ -0,0 +1,98 @@
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WRect = Windows.Foundation.Rect;
namespace CommunityToolkit.Maui.Core.Views;
// We still have to keep this class, due to some random exceptions that occours when the
// .NET MAUI implementation is used.
class WrapperControl : Panel
{
readonly View view;
public WrapperControl(View view, IMauiContext mauiContext)
{
this.view = view;
this.view.MeasureInvalidated += OnMeasureInvalidated;
FrameworkElement = view.ToNative(mauiContext);
Children.Add(FrameworkElement);
// make sure we re-measure once the template is applied
FrameworkElement.Loaded += (sender, args) =>
{
// If the view is a layout (stacklayout, grid, etc) we need to trigger a layout pass
// with all the controls in a consistent native state (i.e., loaded) so they'll actually
// have Bounds set
Handler?.NativeView?.InvalidateMeasure(View);
InvalidateMeasure();
};
}
IView View => view;
INativeViewHandler? Handler => View.Handler as INativeViewHandler;
FrameworkElement FrameworkElement { get; }
public void CleanUp()
{
if (view is not null)
{
view.MeasureInvalidated -= OnMeasureInvalidated;
}
}
protected override global::Windows.Foundation.Size ArrangeOverride(global::Windows.Foundation.Size finalSize)
{
view.IsInNativeLayout = true;
view.Frame = new Rectangle(0, 0, finalSize.Width, finalSize.Height);
FrameworkElement?.Arrange(new WRect(0, 0, finalSize.Width, finalSize.Height));
if (view.Width <= 0 || view.Height <= 0)
{
// Hide Panel when size _view is empty.
// It is necessary that this element does not overlap other elements when it should be hidden.
Opacity = 0;
}
else
{
Opacity = 1;
}
view.IsInNativeLayout = false;
return finalSize;
}
protected override global::Windows.Foundation.Size MeasureOverride(global::Windows.Foundation.Size availableSize)
{
FrameworkElement.Measure(availableSize);
var request = FrameworkElement.DesiredSize;
if (request.Height < 0)
{
request.Height = availableSize.Height;
}
global::Windows.Foundation.Size result;
if (view.HorizontalOptions.Alignment == Microsoft.Maui.Controls.LayoutAlignment.Fill && !double.IsInfinity(availableSize.Width) && availableSize.Width != 0)
{
result = new global::Windows.Foundation.Size(availableSize.Width, request.Height);
}
else
{
result = request;
}
return result;
}
void OnMeasureInvalidated(object? sender, EventArgs e)
{
InvalidateMeasure();
}
}