diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 7a96b76ed0..e80981840c 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -127,6 +127,7 @@ boxmodel BPBF bpmf bpp +Breadcrumb Browsable BROWSEINFO bsd @@ -328,6 +329,7 @@ DEVMODEW DEVMON devpkey DEVSOURCE +DGR DIIRFLAG dimm DISABLEASACTIONKEY @@ -1842,3 +1844,5 @@ zonable zoneset Zoneszonabletester zzz + + diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index 507a815294..b38f6b3757 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -131,7 +131,7 @@ _mm_(?!dd)\w+ # hit-count: 4 file-count: 4 # microsoft -\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]* +\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|developer|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]* aka\.ms/[a-zA-Z0-9]+ diff --git a/src/modules/launcher/PowerLauncher/App.xaml b/src/modules/launcher/PowerLauncher/App.xaml index 92a135daa3..052ea24ea0 100644 --- a/src/modules/launcher/PowerLauncher/App.xaml +++ b/src/modules/launcher/PowerLauncher/App.xaml @@ -8,8 +8,6 @@ - - diff --git a/src/modules/launcher/PowerLauncher/Helper/AnimationFactorToValueConverter.cs b/src/modules/launcher/PowerLauncher/Helper/AnimationFactorToValueConverter.cs new file mode 100644 index 0000000000..3987f1c3bf --- /dev/null +++ b/src/modules/launcher/PowerLauncher/Helper/AnimationFactorToValueConverter.cs @@ -0,0 +1,45 @@ +// 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. + +// https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Fluent/Controls/AnimationFactorToValueConverter.cs +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Fluent.Controls +#pragma warning restore IDE0130 // Namespace does not match folder structure +{ + internal sealed class AnimationFactorToValueConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is not double completeValue) + { + return 0.0; + } + + if (values[1] is not double factor) + { + return 0.0; + } + + if (parameter is "negative") + { + factor = -factor; + } + + return factor * completeValue; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/modules/launcher/PowerLauncher/Helper/FallbackBrushConverter.cs b/src/modules/launcher/PowerLauncher/Helper/FallbackBrushConverter.cs new file mode 100644 index 0000000000..a9e3b06a80 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/Helper/FallbackBrushConverter.cs @@ -0,0 +1,44 @@ +// 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. + +// Copied from https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Fluent/Controls/FallbackBrushConverter.cs +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using System.Windows.Data; + +using System.Windows.Media; + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Fluent.Controls +#pragma warning restore IDE0130 // Namespace does not match folder structure +{ + internal sealed class FallbackBrushConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is SolidColorBrush brush) + { + return brush; + } + + if (value is Color color) + { + return new SolidColorBrush(color); + } + + // We draw red to visibly see an invalid bind in the UI. + return Brushes.Red; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/modules/launcher/PowerLauncher/Helper/ThemeExtensions.cs b/src/modules/launcher/PowerLauncher/Helper/ThemeExtensions.cs index eff513d27d..8332f9f0dc 100644 --- a/src/modules/launcher/PowerLauncher/Helper/ThemeExtensions.cs +++ b/src/modules/launcher/PowerLauncher/Helper/ThemeExtensions.cs @@ -2,46 +2,71 @@ // 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 System.Globalization; 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) + public static Theme GetCurrentTheme() { - return applicationTheme switch + // Check for high-contrast mode + Theme highContrastTheme = GetHighContrastBaseType(); + if (highContrastTheme != Theme.Light) { - ApplicationTheme.Dark => Theme.Dark, - ApplicationTheme.Light => Theme.Light, - ApplicationTheme.HighContrast => GetHighContrastBaseType(), + return highContrastTheme; + } + + // Check if the system is using dark or light mode + return IsSystemDarkMode() ? Theme.Dark : Theme.Light; + } + + private static bool IsSystemDarkMode() + { + const string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; + const string registryValue = "AppsUseLightTheme"; + + // Retrieve the registry value, which is a DWORD (0 or 1) + object registryValueObj = Registry.GetValue(registryKey, registryValue, null); + if (registryValueObj != null) + { + // 0 = Dark mode, 1 = Light mode + bool isLightMode = Convert.ToBoolean((int)registryValueObj, CultureInfo.InvariantCulture); + return !isLightMode; // Invert because 0 = Dark + } + else + { + // Default to Light theme if the registry key is missing + return false; // Default to dark mode assumption + } + } + + public static Theme GetHighContrastBaseType() + { + const string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes"; + const string registryValue = "CurrentTheme"; + + string themePath = (string)Registry.GetValue(registryKey, registryValue, string.Empty); + if (string.IsNullOrEmpty(themePath)) + { + return Theme.Light; // Default to light theme if missing + } + + string theme = themePath.Split('\\').Last().Split('.').First().ToLowerInvariant(); + + return theme switch + { + "hc1" => Theme.HighContrastOne, + "hc2" => Theme.HighContrastTwo, + "hcwhite" => Theme.HighContrastWhite, + "hcblack" => Theme.HighContrastBlack, _ => 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; - } - } } } diff --git a/src/modules/launcher/PowerLauncher/Helper/ThemeManager.cs b/src/modules/launcher/PowerLauncher/Helper/ThemeManager.cs index 7cad29c177..67ad402218 100644 --- a/src/modules/launcher/PowerLauncher/Helper/ThemeManager.cs +++ b/src/modules/launcher/PowerLauncher/Helper/ThemeManager.cs @@ -3,11 +3,12 @@ // See the LICENSE file in the project root for more information. using System; - -using ManagedCommon; +using System.IO; +using System.Windows; +using System.Windows.Media; +using Microsoft.Win32; using Wox.Infrastructure.Image; using Wox.Infrastructure.UserSettings; -using Wpf.Ui.Appearance; namespace PowerLauncher.Helper { @@ -15,10 +16,10 @@ namespace PowerLauncher.Helper { private readonly PowerToysRunSettings _settings; private readonly MainWindow _mainWindow; - private Theme _currentTheme; + private ManagedCommon.Theme _currentTheme; private bool _disposed; - public Theme CurrentTheme => _currentTheme; + public ManagedCommon.Theme CurrentTheme => _currentTheme; public event Common.UI.ThemeChangedHandler ThemeChanged; @@ -26,33 +27,106 @@ namespace PowerLauncher.Helper { _settings = settings; _mainWindow = mainWindow; - _currentTheme = ApplicationThemeManager.GetAppTheme().ToTheme(); - SetTheme(false); - - ApplicationThemeManager.Changed += ApplicationThemeManager_Changed; + UpdateTheme(); + SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged; } - public void SetTheme(bool fromSettings) + private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { - if (_settings.Theme == Theme.Light) + if (e.Category == UserPreferenceCategory.General) { - _currentTheme = Theme.Light; - _mainWindow?.Dispatcher.Invoke(() => ApplicationThemeManager.Apply(ApplicationTheme.Light, _mainWindow.WindowBackdropType)); + // When switching from high contrast to dark mode we have to use UserPreferenceCategory.General otherwise it will crash when loading fluent.xaml + UpdateTheme(); } - else if (_settings.Theme == Theme.Dark) + else if (e.Category == UserPreferenceCategory.Color) { - _currentTheme = Theme.Dark; - _mainWindow?.Dispatcher.Invoke(() => ApplicationThemeManager.Apply(ApplicationTheme.Dark, _mainWindow.WindowBackdropType)); + // https://github.com/dotnet/wpf/issues/10043 When switching to high contrast we have to use UserPreferenceCategory.Color or it will crash due to fluent.xaml being already loaded. + if (_currentTheme is ManagedCommon.Theme.Dark or ManagedCommon.Theme.Light) + { + UpdateTheme(); + } } - else if (fromSettings) + } + + private void SetSystemTheme(ManagedCommon.Theme theme) + { + _mainWindow.Background = Common.UI.OSVersionHelper.IsWindows11() is false ? SystemColors.WindowBrush : null; + + _mainWindow.Resources.MergedDictionaries.Clear(); + _mainWindow.Resources.MergedDictionaries.Add(new ResourceDictionary { - _mainWindow?.Dispatcher.Invoke(ApplicationThemeManager.ApplySystemTheme); + Source = new Uri("Styles/Styles.xaml", UriKind.Relative), + }); + if (theme is ManagedCommon.Theme.Dark or ManagedCommon.Theme.Light) + { + string themeString = theme == ManagedCommon.Theme.Light ? "pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.Light.xaml" + : "pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.Dark.xaml"; + ResourceDictionary fluentThemeDictionary = new() + { + Source = new Uri(themeString, UriKind.Absolute), + }; + _mainWindow.Resources.MergedDictionaries.Add(fluentThemeDictionary); + if (!Common.UI.OSVersionHelper.IsWindows11()) + { + // Apply background only on Windows 10 + // Windows theme does not work properly for dark and light mode so right now set the background color manual. + _mainWindow.Background = new SolidColorBrush + { + Color = theme is ManagedCommon.Theme.Dark ? (Color)ColorConverter.ConvertFromString("#202020") : (Color)ColorConverter.ConvertFromString("#fafafa"), + }; + } + } + else + { + _mainWindow.Resources.MergedDictionaries.Add(new ResourceDictionary + { + Source = new Uri("Styles/FluentHC.xaml", UriKind.Relative), + }); + string styleThemeString = theme switch + { + ManagedCommon.Theme.Light => "Themes/Light.xaml", + ManagedCommon.Theme.Dark => "Themes/Dark.xaml", + ManagedCommon.Theme.HighContrastOne => "Themes/HighContrast1.xaml", + ManagedCommon.Theme.HighContrastTwo => "Themes/HighContrast2.xaml", + ManagedCommon.Theme.HighContrastWhite => "Themes/HighContrastWhite.xaml", + _ => "Themes/HighContrastBlack.xaml", + }; + _mainWindow.Resources.MergedDictionaries.Add(new ResourceDictionary + { + Source = new Uri(styleThemeString, UriKind.Relative), + }); + if (Common.UI.OSVersionHelper.IsWindows11()) + { + // Apply background only on Windows 11 to keep the same style as WPFUI + _mainWindow.Background = new SolidColorBrush + { + Color = (Color)_mainWindow.FindResource("LauncherBackgroundColor"), // Use your DynamicResource key here + }; + } } - ImageLoader.UpdateIconPath(_currentTheme); + ImageLoader.UpdateIconPath(theme); + ThemeChanged?.Invoke(_currentTheme, theme); + _currentTheme = theme; + } - // oldTheme isn't used - ThemeChanged?.Invoke(_currentTheme, _currentTheme); + public void UpdateTheme() + { + ManagedCommon.Theme newTheme = _settings.Theme; + ManagedCommon.Theme theme = ThemeExtensions.GetHighContrastBaseType(); + if (theme != ManagedCommon.Theme.Light) + { + newTheme = theme; + } + else if (_settings.Theme == ManagedCommon.Theme.System) + { + newTheme = ThemeExtensions.GetCurrentTheme(); + } + + _mainWindow.Dispatcher.Invoke(() => + { + SetSystemTheme(newTheme); + }); } public void Dispose() @@ -61,18 +135,6 @@ namespace PowerLauncher.Helper 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) @@ -82,7 +144,7 @@ namespace PowerLauncher.Helper if (disposing) { - ApplicationThemeManager.Changed -= ApplicationThemeManager_Changed; + SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged; } _disposed = true; diff --git a/src/modules/launcher/PowerLauncher/LauncherControl.xaml b/src/modules/launcher/PowerLauncher/LauncherControl.xaml index 365127c0de..8f08537fe0 100644 --- a/src/modules/launcher/PowerLauncher/LauncherControl.xaml +++ b/src/modules/launcher/PowerLauncher/LauncherControl.xaml @@ -6,7 +6,6 @@ xmlns:local="clr-namespace:PowerLauncher" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:p="clr-namespace:PowerLauncher.Properties" - xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" d:DesignHeight="300" d:DesignWidth="720" mc:Ignorable="d"> @@ -40,7 +39,7 @@ - + @@ -115,11 +114,14 @@ x:FieldModifier="public" Canvas.ZIndex="-1" FontSize="{DynamicResource TitleFontSize}" - Foreground="{DynamicResource TextPlaceholderColorBrush}" /> - + + Foreground="{DynamicResource TextControlPlaceholderForeground}" + Text="" /> diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml b/src/modules/launcher/PowerLauncher/MainWindow.xaml index c8f1111e06..5c20c2f304 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml @@ -1,4 +1,4 @@ - - - - - - @@ -71,7 +64,6 @@ - + - - + + diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 8bed73d83c..6bc9ac3b4d 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -25,7 +25,6 @@ using PowerToys.Interop; using Wox.Infrastructure.UserSettings; using Wox.Plugin; using Wox.Plugin.Interfaces; -using Wpf.Ui.Appearance; using CancellationToken = System.Threading.CancellationToken; using Image = Wox.Infrastructure.Image; @@ -50,6 +49,31 @@ namespace PowerLauncher private Point _mouseDownPosition; private ResultViewModel _mouseDownResultViewModel; + // The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set. + public enum DWMWINDOWATTRIBUTE + { + DWMWA_WINDOW_CORNER_PREFERENCE = 33, + } + + // The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function + // what value of the enum to set. + // Copied from dwmapi.h + public enum DWM_WINDOW_CORNER_PREFERENCE + { + DWMWCP_DEFAULT = 0, + DWMWCP_DONOTROUND = 1, + DWMWCP_ROUND = 2, + DWMWCP_ROUNDSMALL = 3, + } + + // Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function. + [DllImport("dwmapi.dll", CharSet = CharSet.Unicode, PreserveSig = false)] + internal static extern void DwmSetWindowAttribute( + IntPtr hwnd, + DWMWINDOWATTRIBUTE attribute, + ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute, + uint cbAttribute); + public MainWindow(PowerToysRunSettings settings, MainViewModel mainVM, CancellationToken nativeWaiterCancelToken) : this() { @@ -63,17 +87,6 @@ namespace PowerLauncher InitializeComponent(); - if (OSVersionHelper.IsWindows11()) - { - WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Acrylic; - } - else - { - WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.None; - } - - SystemThemeWatcher.Watch(this, WindowBackdropType); - _firstDeleteTimer.Elapsed += CheckForFirstDelete; _firstDeleteTimer.Interval = 1000; NativeEventWaiter.WaitForEventLoop( @@ -179,6 +192,14 @@ namespace PowerLauncher // Call RegisterHotKey only after a window handle can be used, so that a global hotkey can be registered. _viewModel.RegisterHotkey(_hwndSource.Handle); + if (OSVersionHelper.IsWindows11()) + { + // ResizeMode="NoResize" removes rounded corners. So force them to rounded. + IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle(); + DWMWINDOWATTRIBUTE attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE; + DWM_WINDOW_CORNER_PREFERENCE preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND; + DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint)); + } } private void OnLoaded(object sender, RoutedEventArgs e) @@ -471,7 +492,7 @@ namespace PowerLauncher private void OnLocationChanged(object sender, EventArgs e) { - if (_settings.RememberLastLaunchLocation) + if (_settings != null && _settings.RememberLastLaunchLocation) { _settings.WindowLeft = Left; _settings.WindowTop = Top; diff --git a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj index b093bbc575..07852dc804 100644 --- a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj +++ b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj @@ -58,7 +58,6 @@ - diff --git a/src/modules/launcher/PowerLauncher/ResultList.xaml b/src/modules/launcher/PowerLauncher/ResultList.xaml index b829d5218f..91523185d9 100644 --- a/src/modules/launcher/PowerLauncher/ResultList.xaml +++ b/src/modules/launcher/PowerLauncher/ResultList.xaml @@ -183,10 +183,11 @@ - + FontSize="14" + Text="{Binding Glyph}" /> diff --git a/src/modules/launcher/PowerLauncher/SettingsReader.cs b/src/modules/launcher/PowerLauncher/SettingsReader.cs index 835ef2bcd4..d70d34a144 100644 --- a/src/modules/launcher/PowerLauncher/SettingsReader.cs +++ b/src/modules/launcher/PowerLauncher/SettingsReader.cs @@ -159,7 +159,7 @@ namespace PowerLauncher if (_settings.Theme != overloadSettings.Properties.Theme) { _settings.Theme = overloadSettings.Properties.Theme; - _themeManager.SetTheme(true); + _themeManager.UpdateTheme(); } if (_settings.StartupPosition != overloadSettings.Properties.Position) diff --git a/src/modules/launcher/PowerLauncher/Styles/FluentHC.xaml b/src/modules/launcher/PowerLauncher/Styles/FluentHC.xaml new file mode 100644 index 0000000000..91fc2b6d1f --- /dev/null +++ b/src/modules/launcher/PowerLauncher/Styles/FluentHC.xaml @@ -0,0 +1,6710 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11,5,11,6 + 1 + 8,0,0,0 + 14 + 22 + 22 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 20 + + 1 + 8,6,0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 14 + 20 + 28 + 40 + 68 + + + + + + + + 1,1,1,1 + 0,0,0,1 + 10,0,0,0 + 0,0,10,0 + 0,0,4,0 + 0,0,0,0 + 24 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9,6,9,8 + 320 + 1 + + + + + + + + + + + + + +