mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-07 03:36:44 +02:00
[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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user