[Launcher]Port from WPF-UI to .NET 9 WPF (#36215)

* Initial implementation

* Fix fluent style

* Fix no endline

* Update expect.txt

* Fix formatting

* Fix light theme looking bad on Windows 10

* fix formatting

* test change

* Now really fixed W10

* Add a comment

* Fix typos

* Fix spellcheck errors

* Fix spellcheck pattern for websites

* Change patterns for spellcheck in the right file

* Fix XAML styling

* Fix contrast colors on W11

* Fix formatting

* Removed emty line

* Fix formatting

* Added comment to fluentHC file

* fix comment

* Fix Windows10 again.
Adress feedback.

* W11 fix chaning from high contrast to normal not having correct background

* W10 Fix high contrast not working after switching from light/dark moed

* Address feedback

* Fix formatting

* Second W11 fix chaning from high contrast to normal not having correct background
This commit is contained in:
Ionuț Manța
2024-12-11 11:47:08 +02:00
committed by GitHub
parent bf3474b134
commit 7c6af6580e
20 changed files with 7098 additions and 142 deletions

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;