Skip to content

taublast/FastPopups

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

93 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FastPopups for .NET MAUI

Simple and fast popups library for .NET MAUI

About

Initially built on top of CommunityToolkit popups version one, it was found to be the fastest for opening popups among different libraries, was definitely worth adding new features like full-screen, navigation stack, hotreload support, animations, and much more.


✨ Key Features

  • πŸ› Crossplatform: iOS, MacCatalyst, Android, Windows
  • ↕️ Display Modes: Default (safe area), Cover (edge-to-edge with bars), FullScreen (hide system UI)
  • πŸŒ– Overlay Customization: Configurable colors and transparency
  • πŸ—‚οΈ Navigation Stack: Thread-safe automatic tracking of all popups
  • πŸ“ Flexible Positioning: Anchored ot aligned positioning
  • πŸ‘† Dismissal Options: Tap-outside-to-dismiss with custom validation
  • πŸ“ Padding Support: Built-in content padding to limit filling content
  • πŸ’ͺ Multiplatform consistency: Same behavior on all platforms
  • πŸ”„ HotReload Support: Preview changes in realtime

⬆️ What's New 1.2.1

Fix MAUI crashing us on Android with "PlatformView cannot be null here", rare case.


Intro

We have 2 layers here: background dimmer and popup content. You can customize the dimmer by setting BackgroundColor of the popup and control the level of tint and transparency. This one will cover all the screen.
When you set popup's layout/padding/anchor properties you are controlling the content. Animations are separate for dimmer and content, first one only uses fade and content is fully animated upon settings, please see below.
If you need to wait until the popup is closed use ShowPopupAsync it can even return a result. For a fire a forget scenario use Show. But wait.. better read the full docs, no?

Please give this a ⭐️ if you find it useful!

πŸš€ Quick Start

Setup

Install from nuget:

dotnet add package AppoMobi.Maui.FastPopups

Initilalize:

using FastPopups;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .AddPopups(); // That's it! πŸŽ‰

        return builder.Build();
    }
}

When in any doubt check out the included SampleApp project!

Create and Show Popups

// Simple popup
var popup = new Popup
{
    Content = new Label { Text = "Hello World!" },
    HeightRequest = 200,
    WidthRequest = 300
};

await this.ShowPopupAsync(popup);

// MVVM popup
var popup = new UserProfilePopup
{
    BindingContext = new UserProfileViewModel(user)
};

await this.ShowPopupAsync(popup);

// Global operations
PopupNavigationStack.Instance.CloseTop(); // Close top popup
PopupNavigationStack.Instance.Clear(); // Close all popups
var count = PopupNavigationStack.Instance.Count; // How many open?

Popup Layers

When rendered your popup is using 2 layers:

  • Background dimmer layer, can be totally transparent or dim your background under popup
  • Popup content

Background Layer

At all times you have a background layer filling the entire screen, layout properties will not affect it. You can change it's color by setting BackgroundColor of the popup, for example:

BackgroundColor="#66000000"

would create a semi-transparent dimmer.

You can hide the layer by setting popup BackgroundColor property to a totally transparent color.

Popup Content

This is your logical popup, layout properties like HorizontalOptions, VerticalOptions will affect its positioning.

DisplayMode controls how the popup content relates to system UI (status bar, navigation bar, etc.):

  • PopupDisplayMode.Default - Content respects safe area insets and positions within safe areas
  • PopupDisplayMode.Cover - Content can extend edge-to-edge while system UI remains visible (great for drawer menus)
  • PopupDisplayMode.FullScreen - System UI is hidden and content can extend fully edge-to-edge (for immersive experiences)

Padding

The Padding property creates internal spacing within your popup, reducing the available content area. This is useful for creating visual breathing room around your content.

var popup = new Popup
{
    Content = new Label { Text = "Content with padding" },
    Padding = new Thickness(50), // 50 pts padding on all sides
    BackgroundColor = Colors.Red, // You'll see the red border around content
    DisplayMode = PopupDisplayMode.FullScreen
};

// Or specify different padding for each side
popup.Padding = new Thickness(left: 10, top: 20, right: 10, bottom: 20);

Key Points:

  • βœ… Content-only: Padding affects content positioning/sizing, not the background overlay
  • βœ… Cross-platform: Works consistently on Windows, iOS, Android, and other platforms
  • βœ… Visual spacing: Creates clean borders between content and popup edges
  • βœ… Layout aware: Works with all layout options (Fill, Center, Start, End)

Animations

FastPopups supports platform-native animations for both appearing and hiding transitions.
You can customize animation type, duration, for some types changing easing curves is also possible.
Best demonstrated in the included SampleApp. Dimmer layer and Content are separately animated, with dimmer fading in/out and content using the selected animation type.

Animation Types

Available animation types:

  • Fade - Simple fade in/out
  • Bottom/Top/Left/Right - Slide from edges
  • SprintBottom/SprintTop/SprintLeft/SprintRight - Slide with elastic overshoot effect and fade.
  • ZoomIn/ZoomOut - Scale animations
  • Whirl - Rotate + scale effect
  • BounceIn/BounceInHorizontal/BounceInVertical - Bounce-in from small (0.5β†’1.1β†’1.0)
  • BounceOut/BounceOutHorizontal/BounceOutVertical - Opposite of BounceIn - bounces in from large (1.5β†’0.9β†’1.0) and out to large (1.0β†’1.1β†’1.5)
  • FlipHorizontal/FlipVertical - 3D flip animations

Animation Properties

var popup = new Popup
{
    Content = myContent,
    AnimationType = PopupAnimationType.SprintBottom, // Animation style
    AnimationDuration = 300, // Duration in milliseconds
    AnimationEasing = PopupAnimationEasing.Decelerate // Easing curve
};

Animation Easing Options

The AnimationEasing property controls how the animation accelerates and decelerates:

  • Default - Platform-specific default (Decelerate for show, Accelerate for hide)
  • Linear - Constant speed throughout
  • Decelerate - Fast start, slow end (ease-out)
  • Accelerate - Slow start, fast end (ease-in)
  • AccelerateDecelerate - Slow-fast-slow S-curve (ease-in-out)
  • Spring - Spring physics with overshoot
  • Elastic - Elastic bounce effect

Cross-Platform Consistency

Animations are implemented using native platform APIs for optimal performance:

  • iOS/macOS: UIViewPropertyAnimator with timing curves
  • Android: ObjectAnimator with interpolators
  • Windows: XAML Storyboard with easing functions

Despite using different native APIs, the visual appearance is consistent across all platforms.

Gestures

When CloseWhenBackgroundIsClicked is set to true then tapping somewhere where gestures are not user-handled will close your popup. You can override OnBackgroundClicked to return false to block closing.

Anchor Positioning

You can position popups relative to specific UI elements. This is particularly useful for context menus, tooltips, or dropdowns that should appear next to buttons or other controls.

When you set the Anchor property of a popup to reference another view, the popup will be positioned relative to that anchor element instead of using the standard alignment properties (HorizontalOptions, VerticalOptions).

First, give your anchor element a name:

<Button x:Name="AnchoredButton" 
        Text="Show Anchored Popup" 
        Clicked="OnAnchoredPopupClicked" />

Then set the anchor when showing the popup:

private async void OnAnchoredPopupClicked(object sender, EventArgs e)
{
    var popup = new AnchoredPopup();
    popup.Anchor = AnchoredButton; // Reference the button
    await this.ShowPopupAsync(popup);
}

Usage Guide

Simple Setup (One Line!)

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .AddPopups(); // That's it! πŸŽ‰

        return builder.Build();
    }
}

Popup Creation Examples

Simple Message Popup

public async Task ShowMessage(string message)
{
    var popup = new Popup
    {
        Content = new StackLayout
        {
            Children =
            {
                new Label { Text = message, HorizontalOptions = LayoutOptions.Center },
                new Button
                {
                    Text = "OK",
                    Command = new Command(() => popup.Close("ok"))
                }
            }
        },
        HeightRequest = 150,
        WidthRequest = 300,
        Padding = new Thickness(20), // Add 20px padding around content
        CloseWhenBackgroundIsClicked = true
    };

    var result = await this.ShowPopupAsync(popup);
}

Popup with Custom Padding

public async Task ShowPaddedDialog()
{
    var popup = new Popup
    {
        Content = new Frame
        {
            BackgroundColor = Colors.White,
            Content = new Label
            {
                Text = "This content has custom padding!",
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center
            }
        },
        Padding = new Thickness(30, 50, 30, 20), // Different padding per side
        BackgroundColor = Colors.Black.WithAlpha(0.7f),
        DisplayMode = PopupDisplayMode.FullScreen,
        CloseWhenBackgroundIsClicked = true
    };

    await this.ShowPopupAsync(popup);
}

Anchored Popup (Context Menu Style)

private async void OnButtonClicked(object sender, EventArgs e)
{
    var button = (Button)sender;

    var popup = new Popup
    {
        Anchor = button, // Position relative to button
        Content = new StackLayout
        {
            Children =
            {
                new Button { Text = "Option 1" },
                new Button { Text = "Option 2" },
                new Button { Text = "Option 3" }
            }
        },
        CloseWhenBackgroundIsClicked = true,
        BackgroundColor = Colors.Transparent // No dimming
    };

    await this.ShowPopupAsync(popup);
}

MVVM Pattern Example

public class MyPageViewModel
{
    public async Task ShowConfirmation()
    {
        // Create popup with ViewModel
        var popup = new ConfirmationPopup
        {
            BindingContext = new ConfirmationViewModel
            {
                Title = "Confirm Action",
                Message = "Are you sure you want to delete this item?"
            },
            HeightRequest = 200,
            WidthRequest = 400
        };

        var result = await Application.Current.MainPage.ShowPopupAsync(popup);

        if (result?.ToString() == "confirmed")
        {
            // User confirmed action
        }
    }

    public async Task ShowComplexDialog()
    {
        // For complex ViewModels, create separately
        var viewModel = new DialogViewModel();
        viewModel.LoadData();
        viewModel.Title = "Custom Title";

        var popup = new DialogPopup
        {
            BindingContext = viewModel,
            HeightRequest = 400,
            WidthRequest = 500
        };

        var result = await Application.Current.MainPage.ShowPopupAsync(popup);
    }
}

PopupNavigationStack Examples

The NavigationStack automatically tracks ALL popups regardless of how they were created:

Basic Stack Operations

// Check current state
var stackCount = PopupNavigationStack.Instance.Count;
var topPopup = PopupNavigationStack.Instance.Peek();

// Global operations (works with ANY popup)
PopupNavigationStack.Instance.Clear(); // Close all popups
var popup = PopupNavigationStack.Instance.Pop(); // Get and remove top popup

// The stack is thread-safe and automatically maintained

Advanced Stack Management

public class PopupManager
{
    public async Task ShowWizardFlow()
    {
        // Show multiple popups in sequence (any creation method)
        var step1 = new Step1Popup();
        await this.ShowPopupAsync(step1);

        var step2 = new Step2Popup();
        await this.ShowPopupAsync(step2);

        var step3 = new Step3Popup();
        await this.ShowPopupAsync(step3);

        // NavigationStack now contains all 3 popups automatically
        Console.WriteLine($"Popups in stack: {PopupNavigationStack.Instance.Count}");
    }

    public void HandleBackButton()
    {
        if (PopupNavigationStack.Instance.Count > 0)
        {
            // Close the topmost popup (like back navigation)
            var topPopup = PopupNavigationStack.Instance.Pop();
            topPopup?.Close();
        }
        else
        {
            // Handle normal back navigation
            Shell.Current.GoToAsync("..");
        }
    }

    public void HandleEmergencyExit()
    {
        // Close all popups immediately (works with any popup type)
        PopupNavigationStack.Instance.Clear();
    }
}

Multiple Popups Example

public class PopupManager
{
    public async Task ShowMultipleDialogs()
    {
        // Show multiple popups - all automatically tracked
        var confirmation = new ConfirmationPopup();
        await Application.Current.MainPage.ShowPopupAsync(confirmation);

        var input = new InputPopup();
        await Application.Current.MainPage.ShowPopupAsync(input);

        // NavigationStack tracks them automatically
        var count = PopupNavigationStack.Instance.Count;

        // Global stack operations
        PopupNavigationStack.Instance.Clear(); // Close all
    }

    public void HandleBackButton()
    {
        // Close top popup on back button
        if (PopupNavigationStack.Instance.Count > 0)
        {
            var topPopup = PopupNavigationStack.Instance.Pop();
            topPopup?.Close();
        }
    }
}

Key Features Summary

🎯 Simple & Powerful

  • βœ… One-line setup - Just .AddPopups() and you're ready
  • βœ… Direct creation - new MyPopup() - no magic, no complexity
  • βœ… Full control - Customize every aspect of your popups
  • βœ… MVVM friendly - Works perfectly with ViewModels and data binding
  • βœ… No dependencies - No services to inject or register

πŸ—‚οΈ NavigationStack Features

  • βœ… Thread-Safe: All operations are thread-safe with internal locking
  • βœ… Automatic Management: Popups are automatically added/removed from stack
  • βœ… Universal Tracking: Works with all popup creation methods
  • βœ… LIFO Behavior: Last-In-First-Out like a standard navigation stack
  • βœ… Bulk Operations: Close multiple popups efficiently
  • βœ… Stack Inspection: Peek at the top popup without removing it
  • βœ… Global Access: Static singleton accessible from anywhere

Why This Approach

βœ… Simplified ❌ Complex Alternatives
One line setup - Just .AddPopups() Multiple service registrations
Direct creation - new MyPopup() Magic string mappings
Full control - Customize everything Limited by service patterns
Easy debugging - See exactly what happens Hidden service magic
No dependencies - Just create and show Inject services everywhere

Real-World Usage

public class RealWorldExample
{
    public async Task ShowUserProfile(User user)
    {
        // Simple, direct, powerful
        var popup = new UserProfilePopup
        {
            BindingContext = new UserProfileViewModel(user),
            HeightRequest = 500,
            WidthRequest = 400,
            CloseWhenBackgroundIsClicked = true
        };

        var result = await Application.Current.MainPage.ShowPopupAsync(popup);

        // NavigationStack automatically tracked everything
        Console.WriteLine($"Popups in stack: {PopupNavigationStack.Instance.Count}");
    }

    public void CloseAllPopups()
    {
        // Global operations work seamlessly
        PopupNavigationStack.Instance.Clear();
    }
}

Advanced Usage

Custom Validation

Override OnBackgroundClicked() to implement custom dismissal logic:

public class ConfirmationPopup : Popup
{
    public bool HasUnsavedChanges { get; set; }

    public override bool OnBackgroundClicked()
    {
        if (HasUnsavedChanges)
        {
            // Show confirmation dialog or prevent closing
            return false; // Prevent dismissal
        }

        return true; // Allow dismissal
    }
}

Examples

Modal Dialog with Result

public class ConfirmDialog : Popup
{
    public ConfirmDialog(string message)
    {
        Content = new StackLayout
        {
            Children =
            {
                new Label { Text = message },
                new Button
                {
                    Text = "OK",
                    Command = new Command(() => Close("confirmed"))
                },
                new Button
                {
                    Text = "Cancel",
                    Command = new Command(() => Close("cancelled"))
                }
            }
        };

        CloseWhenBackgroundIsClicked = false; // Force button interaction
        ResultWhenUserTapsOutsideOfPopup = "cancelled";
    }
}

// Usage
var dialog = new ConfirmDialog("Are you sure?");
var result = await this.ShowPopupAsync(dialog);
if (result?.ToString() == "confirmed")
{
    // User confirmed
}

Context Menu

public class ContextMenu : Popup
{
    public ContextMenu(IView anchor, List<MenuItem> items)
    {
        Anchor = anchor;
        CloseWhenBackgroundIsClicked = true;
        BackgroundColor = Colors.Transparent; // No dimming

        Content = new CollectionView
        {
            ItemsSource = items,
            ItemTemplate = new DataTemplate(() =>
            {
                var label = new Label();
                label.SetBinding(Label.TextProperty, "Title");
                return new ViewCell { View = label };
            })
        };
    }
}

Loading Overlay

public class LoadingPopup : Popup
{
    public LoadingPopup()
    {
        CloseWhenBackgroundIsClicked = false;
        BackgroundColor = Colors.Black.WithAlpha(0.7f);

        Content = new StackLayout
        {
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center,
            Children =
            {
                new ActivityIndicator { IsRunning = true },
                new Label { Text = "Loading...", TextColor = Colors.White }
            }
        };
    }
}

API Reference

Two Simple Ways to Work with Popups

AppoMobi.Maui.FastPopups provides a clean, simple approach with two complementary parts:

1. 🎯 Create and Show Popups (Direct Approach)

Simple, powerful, and gives you full control:

// Create popup manually
var popup = new Popup
{
    Content = new Label { Text = "Hello World!" },
    HeightRequest = 200,
    WidthRequest = 300,
    CloseWhenBackgroundIsClicked = true
};

// Show popup using extension methods
var result = await this.ShowPopupAsync(popup);
// OR
this.ShowPopup(popup);

Perfect for: All scenarios - simple messages, complex forms, MVVM patterns, everything!

2. πŸ—‚οΈ PopupNavigationStack (Global Stack Management)

Automatic tracking and global operations for all popups:

// Access the global navigation stack (automatically tracks ALL popups)
var stackCount = PopupNavigationStack.Instance.Count;
var topPopup = PopupNavigationStack.Instance.Peek();

// Global operations
PopupNavigationStack.Instance.Clear(); // Close all popups
var popup = PopupNavigationStack.Instance.Pop(); // Get and remove top popup

Perfect for: Managing multiple popups, global operations, navigation-like experiences


How They Work Together

Both parts work seamlessly together:

graph TD
    A[Create Popup] --> B[Extension Methods]
    B --> C[Platform Display]
    C --> D[PopupNavigationStack]
    D --> E[Automatic Tracking]
    E --> F[Global Operations]
Loading

Key Points:

  • βœ… All popups are automatically tracked in NavigationStack
  • βœ… Simple and powerful - no complex setup or registration needed
  • βœ… Global operations work on all popups - close all, peek, count, etc.
  • βœ… Automatic cleanup - popups are removed from stack when closed
  • βœ… Full control - customize every aspect of your popups

Popup Properties Reference

var popup = new Popup
{
    // Content and layout
    Content = new Label { Text = "Hello World" },
    HeightRequest = 200,
    WidthRequest = 300,
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center,

    // Background and overlay
    BackgroundColor = Colors.Black.WithAlpha(0.5f), // Semi-transparent dimmer
    Color = Colors.White, // Popup content background

    // Spacing and padding
    Padding = new Thickness(20), // 20px padding on all sides
    // or Padding = new Thickness(10, 20, 10, 20), // left, top, right, bottom

    // Behavior
    CloseWhenBackgroundIsClicked = true,
    DisplayMode = PopupDisplayMode.Default, // or Cover, or FullScreen

    // Positioning
    Anchor = someButton, // Position relative to this element

    // Results
    ResultWhenUserTapsOutsideOfPopup = "cancelled"
};

Please feel free to contribute! The next lib improvement might be to add more background layer customization, mainly thinking about blurring te background with it, instead of placing a blurred screenshot inside the content - what I am actually doing when needed.

About

A simple and fast cross-platform animated popups library for .NET MAUI

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages