[Run] Switch to WPF UI theme manager (#30520)

* Switch to WPF UI theme manager

* fix

* add theme manager

* fix

* fix

* update error icon

* moved image initialization

* cleanup
This commit is contained in:
Davide Giacometti
2023-12-21 13:56:48 +01:00
committed by GitHub
parent e73e73fa6c
commit ae21b0dc09
12 changed files with 153 additions and 123 deletions

View File

@@ -50,15 +50,7 @@ public partial class OCROverlay : Window
InitializeComponent(); InitializeComponent();
// workaround for #30177 Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, Wpf.Ui.Controls.WindowBackdropType.None);
try
{
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, Wpf.Ui.Controls.WindowBackdropType.None);
}
catch (Exception ex)
{
Logger.LogError($"Exception in SystemThemeWatcher.Watch, issue 30177. {ex.Message}");
}
PopulateLanguageMenu(); PopulateLanguageMenu();
} }

View File

@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Common.UI; using Common.UI;
using ImageResizer.ViewModels; using ImageResizer.ViewModels;
using ManagedCommon;
using Microsoft.Win32; using Microsoft.Win32;
using Wpf.Ui.Controls; using Wpf.Ui.Controls;
using AppResources = ImageResizer.Properties.Resources; using AppResources = ImageResizer.Properties.Resources;
@@ -31,15 +30,7 @@ namespace ImageResizer.Views
WindowBackdropType = WindowBackdropType.None; WindowBackdropType = WindowBackdropType.None;
} }
// workaround for #30177 Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, WindowBackdropType);
try
{
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, WindowBackdropType);
}
catch (Exception ex)
{
Logger.LogError($"Exception in SystemThemeWatcher.Watch, issue 30177. {ex.Message}");
}
} }
public IEnumerable<string> OpenPictureFiles() public IEnumerable<string> OpenPictureFiles()

View File

@@ -2,13 +2,11 @@
x:Class="PowerLauncher.App" x:Class="PowerLauncher.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:theming="clr-namespace:Common.UI;assembly=PowerToys.Common.UI"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
ShutdownMode="OnMainWindowClose" ShutdownMode="OnMainWindowClose"
Startup="OnStartup"> Startup="OnStartup">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<theming:CustomLibraryThemeProvider x:Key="{x:Static theming:CustomLibraryThemeProvider.DefaultInstance}" />
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Dark" /> <ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary /> <ui:ControlsDictionary />

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation // Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
@@ -9,7 +9,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using Common.UI;
using interop; using interop;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerLauncher.Telemetry;
@@ -23,7 +22,6 @@ using Wox.Infrastructure.Image;
using Wox.Infrastructure.UserSettings; using Wox.Infrastructure.UserSettings;
using Wox.Plugin; using Wox.Plugin;
using Wox.Plugin.Logger; using Wox.Plugin.Logger;
using Wpf.Ui.Appearance;
using Stopwatch = Wox.Infrastructure.Stopwatch; using Stopwatch = Wox.Infrastructure.Stopwatch;
namespace PowerLauncher namespace PowerLauncher
@@ -82,7 +80,7 @@ namespace PowerLauncher
{ {
application.InitializeComponent(); application.InitializeComponent();
NativeEventWaiter.WaitForEventLoop( Common.UI.NativeEventWaiter.WaitForEventLoop(
Constants.RunExitEvent(), Constants.RunExitEvent(),
() => () =>
{ {
@@ -122,8 +120,7 @@ namespace PowerLauncher
RegisterAppDomainExceptions(); RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException(); RegisterDispatcherUnhandledException();
_themeManager = new ThemeManager(this); ImageLoader.Initialize();
ImageLoader.Initialize(_themeManager.GetCurrentTheme());
_settingsVM = new SettingWindowViewModel(); _settingsVM = new SettingWindowViewModel();
_settings = _settingsVM.Settings; _settings = _settingsVM.Settings;
@@ -136,6 +133,7 @@ namespace PowerLauncher
_mainVM = new MainViewModel(_settings, NativeThreadCTS.Token); _mainVM = new MainViewModel(_settings, NativeThreadCTS.Token);
_mainWindow = new MainWindow(_settings, _mainVM, NativeThreadCTS.Token); _mainWindow = new MainWindow(_settings, _mainVM, NativeThreadCTS.Token);
_themeManager = new ThemeManager(_settings, _mainWindow);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager); API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
_settingsReader = new SettingsReader(_settings, _themeManager); _settingsReader = new SettingsReader(_settings, _themeManager);
_settingsReader.ReadSettings(); _settingsReader.ReadSettings();
@@ -152,10 +150,6 @@ namespace PowerLauncher
_settingsReader.ReadSettingsOnChange(); _settingsReader.ReadSettingsOnChange();
_themeManager.ThemeChanged += OnThemeChanged;
OnThemeChanged(_settings.Theme, _settings.Theme);
textToLog.AppendLine("End PowerToys Run startup ---------------------------------------------------- "); textToLog.AppendLine("End PowerToys Run startup ---------------------------------------------------- ");
bootTime.Stop(); bootTime.Stop();
@@ -214,48 +208,6 @@ namespace PowerLauncher
}; };
} }
/// <summary>
/// Callback when windows theme is changed.
/// </summary>
/// <param name="oldTheme">Previous Theme</param>
/// <param name="newTheme">Current Theme</param>
private void OnThemeChanged(Theme oldTheme, Theme newTheme)
{
// If OS theme is high contrast, don't change theme.
if (SystemParameters.HighContrast)
{
return;
}
ApplicationTheme theme = ApplicationTheme.Unknown;
switch (newTheme)
{
case Theme.Dark:
theme = ApplicationTheme.Dark; break;
case Theme.Light:
theme = ApplicationTheme.Light; break;
case Theme.HighContrastWhite:
case Theme.HighContrastBlack:
case Theme.HighContrastOne:
case Theme.HighContrastTwo:
theme = ApplicationTheme.HighContrast; break;
default:
break;
}
_mainWindow?.Dispatcher.Invoke(() =>
{
if (theme != ApplicationTheme.Unknown)
{
ApplicationThemeManager.Apply(theme);
}
});
ImageLoader.UpdateIconPath(newTheme);
_mainVM.Query();
}
/// <summary> /// <summary>
/// let exception throw as normal is better for Debug /// let exception throw as normal is better for Debug
/// </summary> /// </summary>
@@ -302,19 +254,12 @@ namespace PowerLauncher
Log.Info("Start PowerToys Run Exit---------------------------------------------------- ", GetType()); Log.Info("Start PowerToys Run Exit---------------------------------------------------- ", GetType());
if (disposing) if (disposing)
{ {
if (_themeManager != null)
{
_themeManager.ThemeChanged -= OnThemeChanged;
}
API?.SaveAppAllSettings(); API?.SaveAppAllSettings();
PluginManager.Dispose(); PluginManager.Dispose();
// Dispose needs to be called on the main Windows thread, since some resources owned by the thread need to be disposed. // Dispose needs to be called on the main Windows thread, since some resources owned by the thread need to be disposed.
_mainWindow?.Dispatcher.Invoke(Dispose); _mainWindow?.Dispatcher.Invoke(Dispose);
API?.Dispose();
_mainVM?.Dispose(); _mainVM?.Dispose();
_themeManager?.Dispose();
} }
Log.Info("End PowerToys Run Exit ---------------------------------------------------- ", GetType()); Log.Info("End PowerToys Run Exit ---------------------------------------------------- ", GetType());

View File

@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Linq;
using ManagedCommon;
using Microsoft.Win32;
using Wpf.Ui.Appearance;
namespace PowerLauncher.Helper
{
public static class ThemeExtensions
{
public static Theme ToTheme(this ApplicationTheme applicationTheme)
{
return applicationTheme switch
{
ApplicationTheme.Dark => Theme.Dark,
ApplicationTheme.Light => Theme.Light,
ApplicationTheme.HighContrast => GetHighContrastBaseType(),
_ => Theme.Light,
};
}
private static Theme GetHighContrastBaseType()
{
string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes";
string theme = (string)Registry.GetValue(registryKey, "CurrentTheme", string.Empty);
theme = theme.Split('\\').Last().Split('.').First().ToString();
switch (theme)
{
case "hc1":
return Theme.HighContrastOne;
case "hc2":
return Theme.HighContrastTwo;
case "hcwhite":
return Theme.HighContrastWhite;
case "hcblack":
return Theme.HighContrastBlack;
default:
return Theme.HighContrastOne;
}
}
}
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using ManagedCommon;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.UserSettings;
using Wpf.Ui.Appearance;
namespace PowerLauncher.Helper
{
public class ThemeManager : IDisposable
{
private readonly PowerToysRunSettings _settings;
private readonly MainWindow _mainWindow;
private Theme _currentTheme;
private bool _disposed;
public Theme CurrentTheme => _currentTheme;
public event Common.UI.ThemeChangedHandler ThemeChanged;
public ThemeManager(PowerToysRunSettings settings, MainWindow mainWindow)
{
_settings = settings;
_mainWindow = mainWindow;
_currentTheme = ApplicationThemeManager.GetAppTheme().ToTheme();
SetTheme(false);
ApplicationThemeManager.Changed += ApplicationThemeManager_Changed;
}
public void SetTheme(bool fromSettings)
{
if (_settings.Theme == Theme.Light)
{
_currentTheme = Theme.Light;
_mainWindow?.Dispatcher.Invoke(() => ApplicationThemeManager.Apply(ApplicationTheme.Light, _mainWindow.WindowBackdropType));
}
else if (_settings.Theme == Theme.Dark)
{
_currentTheme = Theme.Dark;
_mainWindow?.Dispatcher.Invoke(() => ApplicationThemeManager.Apply(ApplicationTheme.Dark, _mainWindow.WindowBackdropType));
}
else if (fromSettings)
{
_mainWindow?.Dispatcher.Invoke(ApplicationThemeManager.ApplySystemTheme);
}
ImageLoader.UpdateIconPath(_currentTheme);
// oldTheme isn't used
ThemeChanged?.Invoke(_currentTheme, _currentTheme);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void ApplicationThemeManager_Changed(ApplicationTheme currentApplicationTheme, System.Windows.Media.Color systemAccent)
{
var newTheme = currentApplicationTheme.ToTheme();
if (_currentTheme == newTheme)
{
return;
}
_currentTheme = newTheme;
SetTheme(false);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
ApplicationThemeManager.Changed -= ApplicationThemeManager_Changed;
}
_disposed = true;
}
}
}

View File

@@ -24,6 +24,7 @@ using PowerLauncher.ViewModel;
using Wox.Infrastructure.UserSettings; using Wox.Infrastructure.UserSettings;
using Wox.Plugin; using Wox.Plugin;
using Wox.Plugin.Interfaces; using Wox.Plugin.Interfaces;
using Wpf.Ui.Appearance;
using CancellationToken = System.Threading.CancellationToken; using CancellationToken = System.Threading.CancellationToken;
using Image = Wox.Infrastructure.Image; using Image = Wox.Infrastructure.Image;
using KeyEventArgs = System.Windows.Input.KeyEventArgs; using KeyEventArgs = System.Windows.Input.KeyEventArgs;
@@ -65,15 +66,7 @@ namespace PowerLauncher
WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.None; WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.None;
} }
// workaround for #30217 SystemThemeWatcher.Watch(this, WindowBackdropType);
try
{
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, WindowBackdropType);
}
catch (Exception ex)
{
Log.Exception("Exception in SystemThemeWatcher.Watch, issue 30217.", ex, GetType());
}
_firstDeleteTimer.Elapsed += CheckForFirstDelete; _firstDeleteTimer.Elapsed += CheckForFirstDelete;
_firstDeleteTimer.Interval = 1000; _firstDeleteTimer.Interval = 1000;
@@ -803,11 +796,7 @@ namespace PowerLauncher
{ {
if (disposing) if (disposing)
{ {
if (_firstDeleteTimer != null) _firstDeleteTimer?.Dispose();
{
_firstDeleteTimer.Dispose();
}
_hwndSource?.Dispose(); _hwndSource?.Dispose();
} }

View File

@@ -80,7 +80,6 @@
<PackageReference Include="Microsoft.Data.Sqlite" /> <PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="ModernWpfUI" />
<PackageReference Include="ScipBe.Common.Office.OneNote" /> <PackageReference Include="ScipBe.Common.Office.OneNote" />
<PackageReference Include="System.Reactive" /> <PackageReference Include="System.Reactive" />
<PackageReference Include="System.Data.OleDb" /> <PackageReference Include="System.Data.OleDb" />

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation // Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
@@ -6,18 +6,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using Common.UI;
using ManagedCommon; using ManagedCommon;
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Notifications;
using PowerLauncher.Helper;
using PowerLauncher.Plugin; using PowerLauncher.Plugin;
using PowerLauncher.ViewModel; using PowerLauncher.ViewModel;
using Windows.UI.Notifications; using Windows.UI.Notifications;
using Wox.Infrastructure; using Wox.Infrastructure;
using Wox.Infrastructure.Image; using Wox.Infrastructure.Image;
using Wox.Plugin; using Wox.Plugin;
@@ -32,7 +26,7 @@ namespace Wox
private readonly ThemeManager _themeManager; private readonly ThemeManager _themeManager;
private bool _disposed; private bool _disposed;
public event ThemeChangedHandler ThemeChanged; public event Common.UI.ThemeChangedHandler ThemeChanged;
public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, Alphabet alphabet, ThemeManager themeManager) public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, Alphabet alphabet, ThemeManager themeManager)
{ {
@@ -108,7 +102,7 @@ namespace Wox
public Theme GetCurrentTheme() public Theme GetCurrentTheme()
{ {
return _themeManager.GetCurrentTheme(); return _themeManager.CurrentTheme;
} }
public void Dispose() public void Dispose()

View File

@@ -9,7 +9,6 @@ using System.IO.Abstractions;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Input; using System.Windows.Input;
using Common.UI;
using global::PowerToys.GPOWrapper; using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using PowerLauncher.Helper; using PowerLauncher.Helper;
@@ -44,9 +43,6 @@ namespace PowerLauncher
var overloadSettings = _settingsUtils.GetSettingsOrDefault<PowerLauncherSettings>(PowerLauncherSettings.ModuleName); var overloadSettings = _settingsUtils.GetSettingsOrDefault<PowerLauncherSettings>(PowerLauncherSettings.ModuleName);
UpdateSettings(overloadSettings); UpdateSettings(overloadSettings);
_settingsUtils.SaveSettings(overloadSettings.ToJsonString(), PowerLauncherSettings.ModuleName); _settingsUtils.SaveSettings(overloadSettings.ToJsonString(), PowerLauncherSettings.ModuleName);
// Apply theme at startup
_themeManager.ChangeTheme(_settings.Theme, true);
} }
public void CreateSettingsIfNotExists() public void CreateSettingsIfNotExists()
@@ -161,7 +157,7 @@ namespace PowerLauncher
if (_settings.Theme != overloadSettings.Properties.Theme) if (_settings.Theme != overloadSettings.Properties.Theme)
{ {
_settings.Theme = overloadSettings.Properties.Theme; _settings.Theme = overloadSettings.Properties.Theme;
_themeManager.ChangeTheme(_settings.Theme, true); _themeManager.SetTheme(true);
} }
if (_settings.StartupPosition != overloadSettings.Properties.Position) if (_settings.StartupPosition != overloadSettings.Properties.Position)

View File

@@ -31,7 +31,7 @@ namespace Wox.Infrastructure.Image
private static IImageHashGenerator _hashGenerator; private static IImageHashGenerator _hashGenerator;
public static string ErrorIconPath { get; set; } public static string ErrorIconPath { get; set; } = Constant.LightThemedErrorIcon;
private static readonly string[] ImageExtensions = private static readonly string[] ImageExtensions =
{ {
@@ -54,7 +54,7 @@ namespace Wox.Infrastructure.Image
return fs.Read(buffer, 0, buffer.Length) == buffer.Length && pngSignature.SequenceEqual(buffer); return fs.Read(buffer, 0, buffer.Length) == buffer.Length && pngSignature.SequenceEqual(buffer);
} }
public static void Initialize(Theme theme) public static void Initialize()
{ {
_hashGenerator = new ImageHashGenerator(); _hashGenerator = new ImageHashGenerator();
@@ -86,7 +86,6 @@ namespace Wox.Infrastructure.Image
} }
} }
UpdateIconPath(theme);
Task.Run(() => Task.Run(() =>
{ {
Stopwatch.Normal("ImageLoader.Initialize - Preload images cost", async () => Stopwatch.Normal("ImageLoader.Initialize - Preload images cost", async () =>

View File

@@ -5,7 +5,6 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using ManagedCommon;
using Wpf.Ui.Controls; using Wpf.Ui.Controls;
using Point = PowerAccent.Core.Point; using Point = PowerAccent.Core.Point;
using Size = PowerAccent.Core.Size; using Size = PowerAccent.Core.Size;
@@ -40,15 +39,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
{ {
InitializeComponent(); InitializeComponent();
// workaround for #30177 Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);
try
{
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);
}
catch (Exception ex)
{
Logger.LogError($"Exception in SystemThemeWatcher.Watch, issue 30177. {ex.Message}");
}
Application.Current.MainWindow.ShowActivated = false; Application.Current.MainWindow.ShowActivated = false;
Application.Current.MainWindow.Topmost = true; Application.Current.MainWindow.Topmost = true;