using WPFluent.Controls;
using WPFluent.Interop;
namespace WPFluent.Appearance;
///
/// Automatically updates the application background if the system theme or color is changed. settings work globally and cannot be changed for each .
///
///
/// SystemThemeWatcher.Watch(this as System.Windows.Window); SystemThemeWatcher.UnWatch(this as
/// System.Windows.Window); SystemThemeWatcher.Watch(
/// _serviceProvider.GetRequiredService<MainWindow>() );
///
public static class SystemThemeWatcher
{
private static readonly List _observedWindows = [];
private static void ObserveLoadedHandle(ObservedWindow observedWindow)
{
if(!observedWindow.HasHook)
{
System.Diagnostics.Debug
.WriteLine(
$"INFO | {observedWindow.Handle} ({observedWindow.RootVisual?.Title}) registered as watched window.",
nameof(SystemThemeWatcher));
observedWindow.AddHook(WndProc);
_observedWindows.Add(observedWindow);
}
}
private static void ObserveLoadedWindow(Window window, WindowBackdropType backdrop, bool updateAccents)
{
IntPtr hWnd =
(hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
? throw new InvalidOperationException("Could not get window handle.")
: hWnd;
if(hWnd == IntPtr.Zero)
{
throw new InvalidOperationException("Window handle cannot be empty");
}
ObserveLoadedHandle(new ObservedWindow(hWnd, backdrop, updateAccents));
}
private static void ObserveWindowWhenLoaded(Window window, WindowBackdropType backdrop, bool updateAccents)
{
window.Loaded += (_, _) =>
{
IntPtr hWnd =
(hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
? throw new InvalidOperationException("Could not get window handle.")
: hWnd;
if(hWnd == IntPtr.Zero)
{
throw new InvalidOperationException("Window handle cannot be empty");
}
ObserveLoadedHandle(new ObservedWindow(hWnd, backdrop, updateAccents));
};
}
private static void UpdateObservedWindow(nint hWnd)
{
if(!UnsafeNativeMethods.IsValidWindow(hWnd))
{
return;
}
ObservedWindow? observedWindow = _observedWindows.FirstOrDefault(x => x.Handle == hWnd);
if(observedWindow is null)
{
return;
}
ApplicationThemeManager.ApplySystemTheme(observedWindow.UpdateAccents);
ApplicationTheme currentApplicationTheme = ApplicationThemeManager.GetAppTheme();
System.Diagnostics.Debug
.WriteLine(
$"INFO | {observedWindow.Handle} ({observedWindow.RootVisual?.Title}) triggered the application theme change to {ApplicationThemeManager.GetSystemTheme()}.",
nameof(SystemThemeWatcher));
if(observedWindow.RootVisual is not null)
{
WindowBackgroundManager.UpdateBackground(
observedWindow.RootVisual,
currentApplicationTheme,
observedWindow.Backdrop);
}
}
///
/// Listens to system messages on the application windows.
///
private static IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if(msg == (int)User32.WM.WININICHANGE)
{
UpdateObservedWindow(hWnd);
}
return IntPtr.Zero;
}
///
/// Unwatches the window and removes the hook to receive messages from the system.
///
public static void UnWatch(Window? window)
{
if(window is null)
{
return;
}
if(!window.IsLoaded)
{
throw new InvalidOperationException("You cannot unwatch a window that is not yet loaded.");
}
IntPtr hWnd =
(hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
? throw new InvalidOperationException("Could not get window handle.")
: hWnd;
ObservedWindow? observedWindow = _observedWindows.FirstOrDefault(x => x.Handle == hWnd);
if(observedWindow is null)
{
return;
}
observedWindow.RemoveHook(WndProc);
_ = _observedWindows.Remove(observedWindow);
}
///
/// Watches the and applies the background effect and theme according to the system theme.
///
/// The window that will be updated.
/// Background effect to be applied when changing the theme.
/// If , the accents will be updated when the change is detected.
public static void Watch(
Window? window,
WindowBackdropType backdrop = WindowBackdropType.Mica,
bool updateAccents = true)
{
if(window is null)
{
return;
}
if(window.IsLoaded)
{
ObserveLoadedWindow(window, backdrop, updateAccents);
}
else
{
ObserveWindowWhenLoaded(window, backdrop, updateAccents);
}
if(_observedWindows.Count == 0)
{
System.Diagnostics.Debug
.WriteLine(
$"INFO | {typeof(SystemThemeWatcher)} changed the app theme on initialization.",
nameof(SystemThemeWatcher));
ApplicationThemeManager.ApplySystemTheme(updateAccents);
}
}
}