Skip to content

Instantly share code, notes, and snippets.

@LanceMcCarthy
Last active November 12, 2024 22:36
Show Gist options
  • Save LanceMcCarthy/4954ab92ca44c19eb4316d9d683efd50 to your computer and use it in GitHub Desktop.
Save LanceMcCarthy/4954ab92ca44c19eb4316d9d683efd50 to your computer and use it in GitHub Desktop.
Mica support in .NET MAUI
// ************************************************************************ //
// This implementation is inspired from the official WinUI3 backdrops examples
// https://github.com/microsoft/WinUI-Gallery/blob/winui3/XamlControlsGallery/ControlPagesSampleCode/SystemBackdrops/SystemBackdropsSample2.txt
// *********************************************************************** //
using Microsoft.Maui.LifecycleEvents;
#if WINDOWS10_0_17763_0_OR_GREATER
// The namespace of where the WindowsHelpers class resides
// In older versions of .NET MAUI, this was
//using YOUR_APP.Platforms.Windows
// In newer versions of .NET MAUI, it is now
using YOUR_APP.WinUI;
#endif
namespace YOUR_APP.Maui
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
// **** THIS SECTION IS WHAT IS RELEVANT FOR YOU ************ //
builder.ConfigureLifecycleEvents(events =>
{
#if WINDOWS10_0_17763_0_OR_GREATER
events.AddWindows(wndLifeCycleBuilder =>
{
wndLifeCycleBuilder.OnWindowCreated(window =>
{
window.TryMicaOrAcrylic(); // requires 'using YOUR_APP.WinUI;'
});
});
#endif
});
// ************* END OF RELEVANT SECTION *********** //
return builder.Build();
}
}
}
// **************************************************** //
// Place this class file in your Platforms/Windows folder
// **************************************************** //
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using WinRT;
namespace YOUR_APP.Platforms.Windows
{
public static class WindowHelpers
{
public static void TryMicaOrAcrylic(this Microsoft.UI.Xaml.Window window)
{
var dispatcherQueueHelper = new WindowsSystemDispatcherQueueHelper(); // in Platforms.Windows folder
dispatcherQueueHelper.EnsureWindowsSystemDispatcherQueueController();
// Hooking up the policy object
var configurationSource = new SystemBackdropConfiguration();
configurationSource.IsInputActive = true;
switch (((FrameworkElement)window.Content).ActualTheme)
{
case ElementTheme.Dark:
configurationSource.Theme = SystemBackdropTheme.Dark;
break;
case ElementTheme.Light:
configurationSource.Theme = SystemBackdropTheme.Light;
break;
case ElementTheme.Default:
configurationSource.Theme = SystemBackdropTheme.Default;
break;
}
// Let's try Mica first
if (MicaController.IsSupported())
{
var micaController = new MicaController();
micaController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
micaController.SetSystemBackdropConfiguration(configurationSource);
window.Activated += (object sender, WindowActivatedEventArgs args) =>
{
if (args.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated)
{
// Handle situation where a window is activated and placed on top of other active windows.
if (micaController == null)
{
micaController = new MicaController();
micaController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>()); // Requires 'using WinRT;'
micaController.SetSystemBackdropConfiguration(configurationSource);
}
if (configurationSource != null)
configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
}
};
window.Closed += (object sender, WindowEventArgs args) =>
{
if (micaController != null)
{
micaController.Dispose();
micaController = null;
}
configurationSource = null;
};
}
// If no Mica, maybe we can use Acrylic instead
else if (DesktopAcrylicController.IsSupported())
{
var acrylicController = new DesktopAcrylicController();
acrylicController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
acrylicController.SetSystemBackdropConfiguration(configurationSource);
window.Activated += (object sender, WindowActivatedEventArgs args) =>
{
if (args.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated)
{
// Handle situation where a window is activated and placed on top of other active windows.
if (acrylicController == null)
{
acrylicController = new DesktopAcrylicController();
acrylicController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>()); // Requires 'using WinRT;'
acrylicController.SetSystemBackdropConfiguration(configurationSource);
}
}
if (configurationSource != null)
configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
};
window.Closed += (object sender, WindowEventArgs args) =>
{
if (acrylicController != null)
{
acrylicController.Dispose();
acrylicController = null;
}
configurationSource = null;
};
}
}
}
}
// **************************************************** //
// Place this class file in your Platforms/Windows folder
// **************************************************** //
using System.Runtime.InteropServices;
using Windows.System; // Required for DllImport attribute and DispatcherQueue
namespace YOUR_APP.Maui.Platforms.Windows;
public class WindowsSystemDispatcherQueueHelper
{
[StructLayout(LayoutKind.Sequential)]
struct DispatcherQueueOptions
{
internal int dwSize;
internal int threadType;
internal int apartmentType;
}
[DllImport("CoreMessaging.dll")]
private static extern int CreateDispatcherQueueController([In] DispatcherQueueOptions options, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);
object m_dispatcherQueueController = null;
public void EnsureWindowsSystemDispatcherQueueController()
{
if (DispatcherQueue.GetForCurrentThread() != null)
{
// one already exists, so we'll just use it.
return;
}
if (m_dispatcherQueueController == null)
{
DispatcherQueueOptions options;
options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
options.threadType = 2; // DQTYPE_THREAD_CURRENT
options.apartmentType = 2; // DQTAT_COM_STA
CreateDispatcherQueueController(options, ref m_dispatcherQueueController);
}
}
}
@LanceMcCarthy
Copy link
Author

@kolkonos BOOM! Looks great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment