From 095961402b9ab5aeee3f71204d67e84ecf63bee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Mon, 9 Feb 2026 20:42:01 +0100 Subject: [PATCH] CmdPal: Transparent window (#45159) ## Summary This PR adds: - Backdrop material customization - Alongside acrylic, the following options are now available: - Transparent background - Mica background - Background material opacity - Lets you control how transparent the background is ## Pictures? Pictures! image https://github.com/user-attachments/assets/84e83279-afab-481e-b904-f054318c5d2f image ## PR Checklist - [x] Closes: #44197 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../AppearanceSettingsViewModel.cs | 176 +++++++++++++++-- .../BackdropControllerKind.cs | 41 ++++ .../BackdropStyle.cs | 36 ++++ .../BackdropStyleConfig.cs | 77 ++++++++ .../BackdropStyles.cs | 65 ++++++ .../MainWindowViewModel.cs | 28 +++ .../PreviewBrushKind.cs | 21 ++ .../Services/AcrylicBackdropParameters.cs | 9 - .../Services/BackdropParameters.cs | 29 +++ .../Services/ThemeSnapshot.cs | 17 +- .../SettingsModel.cs | 6 + .../Controls/BlurImageControl.cs | 1 - .../Controls/CommandPalettePreview.xaml | 18 +- .../Controls/CommandPalettePreview.xaml.cs | 112 +++++++++-- .../Microsoft.CmdPal.UI/MainWindow.xaml | 2 +- .../Microsoft.CmdPal.UI/MainWindow.xaml.cs | 185 +++++++++++++++--- .../Services/ColorfulThemeProvider.cs | 24 ++- .../Services/IThemeProvider.cs | 16 +- .../Services/NormalThemeProvider.cs | 21 +- .../Services/ThemeContext.cs | 8 + .../Services/ThemeService.cs | 36 +++- .../Settings/AppearancePage.xaml | 136 ++++++++++--- .../Settings/AppearancePage.xaml.cs | 11 ++ .../Strings/en-us/Resources.resw | 105 +++++++--- 24 files changed, 1023 insertions(+), 157 deletions(-) create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs delete mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs index 71e150a7d2..4de0215311 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs @@ -18,6 +18,8 @@ namespace Microsoft.CmdPal.UI.ViewModels; public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDisposable { + private static readonly Color DefaultTintColor = Color.FromArgb(255, 0, 120, 212); + private static readonly ObservableCollection WindowsColorSwatches = [ // row 0 @@ -128,10 +130,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis OnPropertyChanged(); OnPropertyChanged(nameof(ColorizationModeIndex)); OnPropertyChanged(nameof(IsCustomTintVisible)); - OnPropertyChanged(nameof(IsCustomTintIntensityVisible)); + OnPropertyChanged(nameof(IsColorIntensityVisible)); + OnPropertyChanged(nameof(IsImageTintIntensityVisible)); + OnPropertyChanged(nameof(EffectiveTintIntensity)); OnPropertyChanged(nameof(IsBackgroundControlsVisible)); OnPropertyChanged(nameof(IsNoBackgroundVisible)); OnPropertyChanged(nameof(IsAccentColorControlsVisible)); + OnPropertyChanged(nameof(IsResetButtonVisible)); if (value == ColorizationMode.WindowsAccentColor) { @@ -179,6 +184,19 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis { _settings.CustomThemeColorIntensity = value; OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveTintIntensity)); + Save(); + } + } + + public int BackgroundImageTintIntensity + { + get => _settings.BackgroundImageTintIntensity; + set + { + _settings.BackgroundImageTintIntensity = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveTintIntensity)); Save(); } } @@ -279,12 +297,108 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis }; } + public int BackdropOpacity + { + get => _settings.BackdropOpacity; + set + { + if (_settings.BackdropOpacity != value) + { + _settings.BackdropOpacity = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveBackdropStyle)); + OnPropertyChanged(nameof(EffectiveImageOpacity)); + Save(); + } + } + } + + public int BackdropStyleIndex + { + get => (int)_settings.BackdropStyle; + set + { + var newStyle = (BackdropStyle)value; + if (_settings.BackdropStyle != newStyle) + { + _settings.BackdropStyle = newStyle; + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsBackdropOpacityVisible)); + OnPropertyChanged(nameof(IsMicaBackdropDescriptionVisible)); + OnPropertyChanged(nameof(IsBackgroundSettingsEnabled)); + OnPropertyChanged(nameof(IsBackgroundNotAvailableVisible)); + + if (!IsBackgroundSettingsEnabled) + { + IsColorizationDetailsExpanded = false; + } + + Save(); + } + } + } + + /// + /// Gets whether the backdrop opacity slider should be visible. + /// + public bool IsBackdropOpacityVisible => + BackdropStyles.Get(_settings.BackdropStyle).SupportsOpacity; + + /// + /// Gets whether the backdrop description (for styles without options) should be visible. + /// + public bool IsMicaBackdropDescriptionVisible => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsOpacity; + + /// + /// Gets whether background/colorization settings are available. + /// + public bool IsBackgroundSettingsEnabled => + BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization; + + /// + /// Gets whether the "not available" message should be shown (inverse of IsBackgroundSettingsEnabled). + /// + public bool IsBackgroundNotAvailableVisible => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization; + + public BackdropStyle? EffectiveBackdropStyle + { + get + { + // Return style when transparency/blur is visible (not fully opaque Acrylic) + // - Clear/Mica/MicaAlt/AcrylicThin always show their effect + // - Acrylic shows effect only when opacity < 100 + if (_settings.BackdropStyle != BackdropStyle.Acrylic || _settings.BackdropOpacity < 100) + { + return _settings.BackdropStyle; + } + + return null; + } + } + + public double EffectiveImageOpacity => + EffectiveBackdropStyle is not null + ? (BackgroundImageOpacity / 100f) * Math.Sqrt(_settings.BackdropOpacity / 100.0) + : (BackgroundImageOpacity / 100f); + [ObservableProperty] public partial bool IsColorizationDetailsExpanded { get; set; } public bool IsCustomTintVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image; - public bool IsCustomTintIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image; + public bool IsColorIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor; + + public bool IsImageTintIntensityVisible => _settings.ColorizationMode is ColorizationMode.Image; + + /// + /// Gets the effective tint intensity for the preview, based on the current colorization mode. + /// + public int EffectiveTintIntensity => _settings.ColorizationMode is ColorizationMode.Image + ? _settings.BackgroundImageTintIntensity + : _settings.CustomThemeColorIntensity; public bool IsBackgroundControlsVisible => _settings.ColorizationMode is ColorizationMode.Image; @@ -292,16 +406,21 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis public bool IsAccentColorControlsVisible => _settings.ColorizationMode is ColorizationMode.WindowsAccentColor; - public AcrylicBackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f); + public bool IsResetButtonVisible => _settings.ColorizationMode is ColorizationMode.Image; + + public BackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f); public ElementTheme EffectiveTheme => _elementThemeOverride ?? _themeService.Current.Theme; - public Color EffectiveThemeColor => ColorizationMode switch - { - ColorizationMode.WindowsAccentColor => _currentSystemAccentColor, - ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor, - _ => Colors.Transparent, - }; + public Color EffectiveThemeColor => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization + ? Colors.Transparent + : ColorizationMode switch + { + ColorizationMode.WindowsAccentColor => _currentSystemAccentColor, + ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor, + _ => Colors.Transparent, + }; // Since the blur amount is absolute, we need to scale it down for the preview (which is smaller than full screen). public int EffectiveBackgroundImageBlurAmount => (int)Math.Round(BackgroundImageBlurAmount / 4f); @@ -309,11 +428,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis public double EffectiveBackgroundImageBrightness => BackgroundImageBrightness / 100.0; public ImageSource? EffectiveBackgroundImageSource => - ColorizationMode is ColorizationMode.Image - && !string.IsNullOrWhiteSpace(BackgroundImagePath) - && Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri) - ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri) - : null; + !BackdropStyles.Get(_settings.BackdropStyle).SupportsBackgroundImage + ? null + : ColorizationMode is ColorizationMode.Image + && !string.IsNullOrWhiteSpace(BackgroundImagePath) + && Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri) + ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri) + : null; public AppearanceSettingsViewModel(IThemeService themeService, SettingsModel settings) { @@ -327,7 +448,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis Reapply(); - IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None; + IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled; } private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender)); @@ -357,6 +478,8 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis // Theme services recalculates effective color and opacity based on current settings. EffectiveBackdrop = _themeService.Current.BackdropParameters; OnPropertyChanged(nameof(EffectiveBackdrop)); + OnPropertyChanged(nameof(EffectiveBackdropStyle)); + OnPropertyChanged(nameof(EffectiveImageOpacity)); OnPropertyChanged(nameof(EffectiveBackgroundImageBrightness)); OnPropertyChanged(nameof(EffectiveBackgroundImageSource)); OnPropertyChanged(nameof(EffectiveThemeColor)); @@ -379,7 +502,28 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis BackgroundImageBlurAmount = 0; BackgroundImageFit = BackgroundImageFit.UniformToFill; BackgroundImageOpacity = 100; - ColorIntensity = 0; + BackgroundImageTintIntensity = 0; + } + + [RelayCommand] + private void ResetAppearanceSettings() + { + // Reset theme + Theme = UserTheme.Default; + + // Reset backdrop settings + BackdropStyleIndex = (int)BackdropStyle.Acrylic; + BackdropOpacity = 100; + + // Reset background image settings + BackgroundImagePath = string.Empty; + ResetBackgroundImageProperties(); + + // Reset colorization + ColorizationMode = ColorizationMode.None; + ThemeColor = DefaultTintColor; + ColorIntensity = 100; + BackgroundImageTintIntensity = 0; } public void Dispose() diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs new file mode 100644 index 0000000000..9d24d5d435 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs @@ -0,0 +1,41 @@ +// 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. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the type of system backdrop controller to use. +/// +public enum BackdropControllerKind +{ + /// + /// Solid color with alpha transparency (TransparentTintBackdrop). + /// + Solid, + + /// + /// Desktop Acrylic with default blur (DesktopAcrylicKind.Default). + /// + Acrylic, + + /// + /// Desktop Acrylic with thinner blur (DesktopAcrylicKind.Thin). + /// + AcrylicThin, + + /// + /// Mica effect (MicaKind.Base). + /// + Mica, + + /// + /// Mica alternate/darker variant (MicaKind.BaseAlt). + /// + MicaAlt, + + /// + /// Custom backdrop implementation. + /// + Custom, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs new file mode 100644 index 0000000000..72d2835e48 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs @@ -0,0 +1,36 @@ +// 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. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the visual backdrop style for the window. +/// +public enum BackdropStyle +{ + /// + /// Standard desktop acrylic with blur effect. + /// + Acrylic, + + /// + /// Solid color with alpha transparency (no blur). + /// + Clear, + + /// + /// Mica effect that samples the desktop wallpaper. + /// + Mica, + + /// + /// Thinner acrylic variant with more transparency. + /// + AcrylicThin, + + /// + /// Mica alternate variant (darker). + /// + MicaAlt, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs new file mode 100644 index 0000000000..c9fa50a23d --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs @@ -0,0 +1,77 @@ +// 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. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Configuration parameters for a backdrop style. +/// +public sealed record BackdropStyleConfig +{ + /// + /// Gets the type of system backdrop controller to use. + /// + public required BackdropControllerKind ControllerKind { get; init; } + + /// + /// Gets the base tint opacity before user adjustments. + /// + public required float BaseTintOpacity { get; init; } + + /// + /// Gets the base luminosity opacity before user adjustments. + /// + public required float BaseLuminosityOpacity { get; init; } + + /// + /// Gets the brush type to use for preview approximation. + /// + public required PreviewBrushKind PreviewBrush { get; init; } + + /// + /// Gets the fixed opacity for styles that don't support user adjustment (e.g., Mica). + /// When is false, this value is used as the effective opacity. + /// + public float FixedOpacity { get; init; } + + /// + /// Gets whether this backdrop style supports custom colorization (tint colors). + /// + public bool SupportsColorization { get; init; } = true; + + /// + /// Gets whether this backdrop style supports custom background images. + /// + public bool SupportsBackgroundImage { get; init; } = true; + + /// + /// Gets whether this backdrop style supports opacity adjustment. + /// + public bool SupportsOpacity { get; init; } = true; + + /// + /// Computes the effective tint opacity based on this style's configuration. + /// + /// User's backdrop opacity setting (0-1 normalized). + /// Optional override for base tint opacity (used by colorful theme). + /// The effective opacity to apply. + public float ComputeEffectiveOpacity(float userOpacity, float? baseTintOpacityOverride = null) + { + // For styles that don't support opacity (Mica), use FixedOpacity + if (!SupportsOpacity && FixedOpacity > 0) + { + return FixedOpacity; + } + + // For Solid: only user opacity matters (controls alpha of solid color) + if (ControllerKind == BackdropControllerKind.Solid) + { + return userOpacity; + } + + // For blur effects: multiply base opacity with user opacity + var baseTint = baseTintOpacityOverride ?? BaseTintOpacity; + return baseTint * userOpacity; + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs new file mode 100644 index 0000000000..6ba46c156e --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs @@ -0,0 +1,65 @@ +// 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. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Central registry of backdrop style configurations. +/// +public static class BackdropStyles +{ + private static readonly Dictionary Configs = new() + { + [BackdropStyle.Acrylic] = new() + { + ControllerKind = BackdropControllerKind.Acrylic, + BaseTintOpacity = 0.5f, + BaseLuminosityOpacity = 0.9f, + PreviewBrush = PreviewBrushKind.Acrylic, + }, + [BackdropStyle.AcrylicThin] = new() + { + ControllerKind = BackdropControllerKind.AcrylicThin, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 0.85f, + PreviewBrush = PreviewBrushKind.Acrylic, + }, + [BackdropStyle.Mica] = new() + { + ControllerKind = BackdropControllerKind.Mica, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + FixedOpacity = 0.96f, + SupportsOpacity = false, + }, + [BackdropStyle.MicaAlt] = new() + { + ControllerKind = BackdropControllerKind.MicaAlt, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + FixedOpacity = 0.98f, + SupportsOpacity = false, + }, + [BackdropStyle.Clear] = new() + { + ControllerKind = BackdropControllerKind.Solid, + BaseTintOpacity = 1.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + }, + }; + + /// + /// Gets the configuration for the specified backdrop style. + /// + public static BackdropStyleConfig Get(BackdropStyle style) => + Configs.TryGetValue(style, out var config) ? config : Configs[BackdropStyle.Acrylic]; + + /// + /// Gets all registered backdrop styles. + /// + public static IEnumerable All => Configs.Keys; +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs index 140811c784..4d775083f0 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs @@ -22,6 +22,7 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable public partial Stretch BackgroundImageStretch { get; private set; } = Stretch.Fill; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] public partial double BackgroundImageOpacity { get; private set; } [ObservableProperty] @@ -39,6 +40,30 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable [ObservableProperty] public partial bool ShowBackgroundImage { get; private set; } + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveBackdropStyle))] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] + public partial BackdropStyle BackdropStyle { get; private set; } + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveBackdropStyle))] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] + public partial float BackdropOpacity { get; private set; } = 1.0f; + + // Returns null when no transparency needed (BlurImageControl uses this to decide source type) + public BackdropStyle? EffectiveBackdropStyle => + BackdropStyle == BackdropStyle.Clear || + BackdropStyle == BackdropStyle.Mica || + BackdropOpacity < 1.0f + ? BackdropStyle + : null; + + // When transparency is enabled, use square root curve so image stays visible longer as backdrop fades + public double EffectiveImageOpacity => + EffectiveBackdropStyle is not null + ? BackgroundImageOpacity * Math.Sqrt(BackdropOpacity) + : BackgroundImageOpacity; + public MainWindowViewModel(IThemeService themeService) { _themeService = themeService; @@ -58,6 +83,9 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable BackgroundImageTintIntensity = _themeService.Current.TintIntensity; BackgroundImageBlurAmount = _themeService.Current.BlurAmount; + BackdropStyle = _themeService.Current.BackdropParameters.Style; + BackdropOpacity = _themeService.Current.BackdropOpacity; + ShowBackgroundImage = BackgroundImageSource != null; }); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs new file mode 100644 index 0000000000..2e8a644a28 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs @@ -0,0 +1,21 @@ +// 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. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the brush type to use for backdrop preview approximation. +/// +public enum PreviewBrushKind +{ + /// + /// SolidColorBrush with computed alpha. + /// + Solid, + + /// + /// AcrylicBrush with blur effect. + /// + Acrylic, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs deleted file mode 100644 index efb7ca1fa1..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs +++ /dev/null @@ -1,9 +0,0 @@ -// 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 Windows.UI; - -namespace Microsoft.CmdPal.UI.ViewModels.Services; - -public sealed record AcrylicBackdropParameters(Color TintColor, Color FallbackColor, float TintOpacity, float LuminosityOpacity); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs new file mode 100644 index 0000000000..dde4df0e0e --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs @@ -0,0 +1,29 @@ +// 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 Windows.UI; + +namespace Microsoft.CmdPal.UI.ViewModels.Services; + +/// +/// Parameters for configuring the window backdrop appearance. +/// +/// The tint color applied to the backdrop. +/// The fallback color when backdrop effects are unavailable. +/// +/// The effective opacity for the backdrop, pre-computed by the theme provider. +/// For Acrylic style: TintOpacity * BackdropOpacity. +/// For Clear style: BackdropOpacity (controls the solid color alpha). +/// +/// +/// The effective luminosity opacity for Acrylic backdrop, pre-computed by the theme provider. +/// Computed as LuminosityOpacity * BackdropOpacity. +/// +/// The backdrop style (Acrylic or Clear). +public sealed record BackdropParameters( + Color TintColor, + Color FallbackColor, + float EffectiveOpacity, + float EffectiveLuminosityOpacity, + BackdropStyle Style = BackdropStyle.Acrylic); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs index 244fd41fba..a82484d52f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs @@ -51,12 +51,23 @@ public sealed class ThemeSnapshot public required double BackgroundImageOpacity { get; init; } /// - /// Gets the effective acrylic backdrop parameters based on current settings and theme. + /// Gets the effective backdrop parameters based on current settings and theme. /// - /// The resolved AcrylicBackdropParameters to apply. - public required AcrylicBackdropParameters BackdropParameters { get; init; } + /// The resolved BackdropParameters to apply. + public required BackdropParameters BackdropParameters { get; init; } + + /// + /// Gets the raw backdrop opacity setting (0-1 range). + /// Used for determining if transparency is enabled and for image opacity calculations. + /// + public required float BackdropOpacity { get; init; } public required int BlurAmount { get; init; } public required float BackgroundBrightness { get; init; } + + /// + /// Gets whether colorization is active (accent color, custom color, or image mode). + /// + public required bool HasColorization { get; init; } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs index 483fe3fdc3..c023c3daae 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs @@ -74,6 +74,8 @@ public partial class SettingsModel : ObservableObject public int CustomThemeColorIntensity { get; set; } = 100; + public int BackgroundImageTintIntensity { get; set; } + public int BackgroundImageOpacity { get; set; } = 20; public int BackgroundImageBlurAmount { get; set; } @@ -84,6 +86,10 @@ public partial class SettingsModel : ObservableObject public string? BackgroundImagePath { get; set; } + public BackdropStyle BackdropStyle { get; set; } + + public int BackdropOpacity { get; set; } = 100; + // END SETTINGS /////////////////////////////////////////////////////////////////////////// diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/BlurImageControl.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/BlurImageControl.cs index d77cab0645..3d52042751 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/BlurImageControl.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/BlurImageControl.cs @@ -288,7 +288,6 @@ internal sealed partial class BlurImageControl : Control _effectBrush?.Dispose(); _effectBrush = effectFactory.CreateBrush(); - // Set initial source if (ImageSource is not null) { _imageBrush ??= _compositor.CreateSurfaceBrush(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandPalettePreview.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandPalettePreview.xaml index a30d1fafdf..33fbacb839 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandPalettePreview.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandPalettePreview.xaml @@ -16,24 +16,38 @@ CornerRadius="8" Translation="0,0,8"> + + Visibility="{x:Bind ClearVisibility, Mode=OneWay}"> + + + + + + + TintOpacity="{x:Bind PreviewEffectiveOpacity, Mode=OneWay}" /> + (double)GetValue(PreviewBackgroundImageOpacityProperty); + set => SetValue(PreviewBackgroundImageOpacityProperty, value); } public double PreviewBackgroundImageBrightness @@ -92,12 +95,48 @@ public sealed partial class CommandPalettePreview : UserControl set => SetValue(ShowBackgroundImageProperty, value); } + public BackdropStyle? PreviewBackdropStyle + { + get => (BackdropStyle?)GetValue(PreviewBackdropStyleProperty); + set => SetValue(PreviewBackdropStyleProperty, value); + } + + /// + /// Gets or sets the effective opacity for the backdrop, pre-computed by the theme provider. + /// For Acrylic style: used directly as TintOpacity. + /// For Clear style: used to compute the alpha channel of the solid color. + /// + public double PreviewEffectiveOpacity + { + get => (double)GetValue(PreviewEffectiveOpacityProperty); + set => SetValue(PreviewEffectiveOpacityProperty, value); + } + + // Computed read-only properties + public Color EffectiveClearColor + { + get => (Color)GetValue(EffectiveClearColorProperty); + private set => SetValue(EffectiveClearColorProperty, value); + } + + public Visibility AcrylicVisibility + { + get => (Visibility)GetValue(AcrylicVisibilityProperty); + private set => SetValue(AcrylicVisibilityProperty, value); + } + + public Visibility ClearVisibility + { + get => (Visibility)GetValue(ClearVisibilityProperty); + private set => SetValue(ClearVisibilityProperty, value); + } + public CommandPalettePreview() { InitializeComponent(); } - private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnBackgroundImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not CommandPalettePreview preview) { @@ -107,7 +146,46 @@ public sealed partial class CommandPalettePreview : UserControl preview.ShowBackgroundImage = e.NewValue is ImageSource ? Visibility.Visible : Visibility.Collapsed; } - private double ToOpacity(int value) => value / 100.0; + private static void OnBackdropPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not CommandPalettePreview preview) + { + return; + } + + preview.UpdateComputedClearColor(); + } + + private static void OnVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not CommandPalettePreview preview) + { + return; + } + + preview.UpdateComputedVisibilityProperties(); + preview.UpdateComputedClearColor(); + } + + private void UpdateComputedClearColor() + { + EffectiveClearColor = Color.FromArgb( + (byte)(PreviewEffectiveOpacity * 255), + PreviewBackgroundColor.R, + PreviewBackgroundColor.G, + PreviewBackgroundColor.B); + } + + private void UpdateComputedVisibilityProperties() + { + var config = BackdropStyles.Get(PreviewBackdropStyle ?? BackdropStyle.Acrylic); + + // Show backdrop effect based on style (on top of any background image) + AcrylicVisibility = config.PreviewBrush == PreviewBrushKind.Acrylic + ? Visibility.Visible : Visibility.Collapsed; + ClearVisibility = config.PreviewBrush == PreviewBrushKind.Solid + ? Visibility.Visible : Visibility.Collapsed; + } private double ToTintIntensity(int value) => value / 100.0; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml index 32329e17a0..74c736b471 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml @@ -22,7 +22,7 @@ VerticalAlignment="Stretch" BlurAmount="{x:Bind ViewModel.BackgroundImageBlurAmount, Mode=OneWay}" ImageBrightness="{x:Bind ViewModel.BackgroundImageBrightness, Mode=OneWay}" - ImageOpacity="{x:Bind ViewModel.BackgroundImageOpacity, Mode=OneWay}" + ImageOpacity="{x:Bind ViewModel.EffectiveImageOpacity, Mode=OneWay}" ImageSource="{x:Bind ViewModel.BackgroundImageSource, Mode=OneWay}" ImageStretch="{x:Bind ViewModel.BackgroundImageStretch, Mode=OneWay}" IsHitTestVisible="False" diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index d54fbe93d2..10139a94d1 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -31,6 +31,7 @@ using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Graphics; using Windows.System; +using Windows.UI; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; @@ -80,7 +81,9 @@ public sealed partial class MainWindow : WindowEx, private int _sessionErrorCount; private DesktopAcrylicController? _acrylicController; + private MicaController? _micaController; private SystemBackdropConfiguration? _configurationSource; + private bool _isUpdatingBackdrop; private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan; private WindowPosition _currentWindowPosition = new(); @@ -109,7 +112,7 @@ public sealed partial class MainWindow : WindowEx, CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value); } - SetAcrylic(); + InitializeBackdropSupport(); _hiddenOwnerBehavior.ShowInTaskbar(this, Debugger.IsAttached); @@ -158,7 +161,7 @@ public sealed partial class MainWindow : WindowEx, App.Current.Services.GetService()!.SettingsChanged += SettingsChangedHandler; // Make sure that we update the acrylic theme when the OS theme changes - RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic); + RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateBackdrop); // Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () => @@ -185,7 +188,7 @@ public sealed partial class MainWindow : WindowEx, private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e) { - UpdateAcrylic(); + UpdateBackdrop(); } private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e) @@ -280,48 +283,170 @@ public sealed partial class MainWindow : WindowEx, _autoGoHomeTimer.Interval = _autoGoHomeInterval; } - private void SetAcrylic() + private void InitializeBackdropSupport() { - if (DesktopAcrylicController.IsSupported()) + if (DesktopAcrylicController.IsSupported() || MicaController.IsSupported()) { - // Hooking up the policy object. _configurationSource = new SystemBackdropConfiguration { - // Initial configuration state. IsInputActive = true, }; - UpdateAcrylic(); } } - private void UpdateAcrylic() + private void UpdateBackdrop() { + // Prevent re-entrance when backdrop changes trigger ActualThemeChanged + if (_isUpdatingBackdrop) + { + return; + } + + _isUpdatingBackdrop = true; + + var backdrop = _themeService.Current.BackdropParameters; + var isImageMode = ViewModel.ShowBackgroundImage; + var config = BackdropStyles.Get(backdrop.Style); + try { - if (_acrylicController != null) + switch (config.ControllerKind) { - _acrylicController.RemoveAllSystemBackdropTargets(); - _acrylicController.Dispose(); + case BackdropControllerKind.Solid: + CleanupBackdropControllers(); + var tintColor = Color.FromArgb( + (byte)(backdrop.EffectiveOpacity * 255), + backdrop.TintColor.R, + backdrop.TintColor.G, + backdrop.TintColor.B); + SetupTransparentBackdrop(tintColor); + break; + + case BackdropControllerKind.Mica: + case BackdropControllerKind.MicaAlt: + SetupMica(backdrop, isImageMode, config.ControllerKind); + break; + + case BackdropControllerKind.Acrylic: + case BackdropControllerKind.AcrylicThin: + default: + SetupDesktopAcrylic(backdrop, isImageMode, config.ControllerKind); + break; } - - var backdrop = _themeService.Current.BackdropParameters; - _acrylicController = new DesktopAcrylicController - { - TintColor = backdrop.TintColor, - TintOpacity = backdrop.TintOpacity, - FallbackColor = backdrop.FallbackColor, - LuminosityOpacity = backdrop.LuminosityOpacity, - }; - - // Enable the system backdrop. - // Note: Be sure to have "using WinRT;" to support the Window.As<...>() call. - _acrylicController.AddSystemBackdropTarget(this.As()); - _acrylicController.SetSystemBackdropConfiguration(_configurationSource); } catch (Exception ex) { Logger.LogError("Failed to update backdrop", ex); } + finally + { + _isUpdatingBackdrop = false; + } + } + + private void SetupTransparentBackdrop(Color tintColor) + { + if (SystemBackdrop is TransparentTintBackdrop existingBackdrop) + { + existingBackdrop.TintColor = tintColor; + } + else + { + SystemBackdrop = new TransparentTintBackdrop { TintColor = tintColor }; + } + } + + private void CleanupBackdropControllers() + { + if (_acrylicController is not null) + { + _acrylicController.RemoveAllSystemBackdropTargets(); + _acrylicController.Dispose(); + _acrylicController = null; + } + + if (_micaController is not null) + { + _micaController.RemoveAllSystemBackdropTargets(); + _micaController.Dispose(); + _micaController = null; + } + } + + private void SetupDesktopAcrylic(BackdropParameters backdrop, bool isImageMode, BackdropControllerKind kind) + { + CleanupBackdropControllers(); + + // Fall back to solid color if acrylic not supported + if (_configurationSource is null || !DesktopAcrylicController.IsSupported()) + { + SetupTransparentBackdrop(backdrop.FallbackColor); + return; + } + + // DesktopAcrylicController and SystemBackdrop can't be active simultaneously + SystemBackdrop = null; + + // Image mode: no tint here, BlurImageControl handles it (avoids double-tinting) + var effectiveTintOpacity = isImageMode + ? 0.0f + : backdrop.EffectiveOpacity; + + _acrylicController = new DesktopAcrylicController + { + Kind = kind == BackdropControllerKind.AcrylicThin + ? DesktopAcrylicKind.Thin + : DesktopAcrylicKind.Default, + TintColor = backdrop.TintColor, + TintOpacity = effectiveTintOpacity, + FallbackColor = backdrop.FallbackColor, + LuminosityOpacity = backdrop.EffectiveLuminosityOpacity, + }; + + // Requires "using WinRT;" for Window.As<>() + _acrylicController.AddSystemBackdropTarget(this.As()); + _acrylicController.SetSystemBackdropConfiguration(_configurationSource); + } + + private void SetupMica(BackdropParameters backdrop, bool isImageMode, BackdropControllerKind kind) + { + CleanupBackdropControllers(); + + // Fall back to solid color if Mica not supported + if (_configurationSource is null || !MicaController.IsSupported()) + { + SetupTransparentBackdrop(backdrop.FallbackColor); + return; + } + + // MicaController and SystemBackdrop can't be active simultaneously + SystemBackdrop = null; + _configurationSource.Theme = _themeService.Current.Theme == ElementTheme.Dark + ? SystemBackdropTheme.Dark + : SystemBackdropTheme.Light; + + var hasColorization = _themeService.Current.HasColorization || isImageMode; + + _micaController = new MicaController + { + Kind = kind == BackdropControllerKind.MicaAlt + ? MicaKind.BaseAlt + : MicaKind.Base, + }; + + // Only set tint properties when colorization is active + // Otherwise let system handle light/dark theme defaults automatically + if (hasColorization) + { + // Image mode: no tint here, BlurImageControl handles it (avoids double-tinting) + _micaController.TintColor = backdrop.TintColor; + _micaController.TintOpacity = isImageMode ? 0.0f : backdrop.EffectiveOpacity; + _micaController.FallbackColor = backdrop.FallbackColor; + _micaController.LuminosityOpacity = backdrop.EffectiveLuminosityOpacity; + } + + _micaController.AddSystemBackdropTarget(this.As()); + _micaController.SetSystemBackdropConfiguration(_configurationSource); } private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target) @@ -637,12 +762,8 @@ public sealed partial class MainWindow : WindowEx, private void DisposeAcrylic() { - if (_acrylicController is not null) - { - _acrylicController.Dispose(); - _acrylicController = null!; - _configurationSource = null!; - } + CleanupBackdropControllers(); + _configurationSource = null!; } // Updates our window s.t. the top of the window is draggable. diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs index fe6c8e48e0..8e2a5748bd 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs @@ -4,6 +4,7 @@ using CommunityToolkit.WinUI.Helpers; using Microsoft.CmdPal.UI.Helpers; +using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.UI.Xaml; using Windows.UI; @@ -34,7 +35,7 @@ internal sealed class ColorfulThemeProvider : IThemeProvider _uiSettings = uiSettings; } - public AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context) + public BackdropParameters GetBackdropParameters(ThemeContext context) { var isLight = context.Theme == ElementTheme.Light || (context.Theme == ElementTheme.Default && @@ -53,7 +54,26 @@ internal sealed class ColorfulThemeProvider : IThemeProvider var colorIntensity = isLight ? 0.6f * colorIntensityUser : colorIntensityUser; var effectiveBgColor = ColorBlender.Blend(baseColor, blended, colorIntensity); - return new AcrylicBackdropParameters(effectiveBgColor, effectiveBgColor, 0.8f, 0.8f); + var transparencyMode = context.BackdropStyle ?? BackdropStyle.Acrylic; + var config = BackdropStyles.Get(transparencyMode); + + // For colorful theme, boost tint opacity to show color better through blur + // But not for styles with fixed opacity (Mica) - they handle their own opacity + var baseTintOpacity = config.ControllerKind == BackdropControllerKind.Solid || !config.SupportsOpacity + ? (float?)null // Use default + : Math.Max(config.BaseTintOpacity, 0.8f); + + var effectiveOpacity = config.ComputeEffectiveOpacity(context.BackdropOpacity, baseTintOpacity); + var effectiveLuminosityOpacity = config.SupportsOpacity + ? config.BaseLuminosityOpacity * context.BackdropOpacity + : config.BaseLuminosityOpacity; + + return new BackdropParameters( + TintColor: effectiveBgColor, + FallbackColor: effectiveBgColor, + EffectiveOpacity: effectiveOpacity, + EffectiveLuminosityOpacity: effectiveLuminosityOpacity, + Style: transparencyMode); } private static class ColorBlender diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/IThemeProvider.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/IThemeProvider.cs index a9411c3656..af7e869e20 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/IThemeProvider.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/IThemeProvider.cs @@ -8,14 +8,14 @@ using Microsoft.CmdPal.UI.ViewModels.Services; namespace Microsoft.CmdPal.UI.Services; /// -/// Provides theme identification, resource path resolution, and creation of acrylic -/// backdrop parameters based on the current . +/// Provides theme identification, resource path resolution, and creation of backdrop +/// parameters based on the current . /// /// /// Implementations should expose a stable and a valid XAML resource /// dictionary path via . The -/// method computes -/// using the supplied theme context. +/// method computes +/// using the supplied theme context. /// internal interface IThemeProvider { @@ -30,9 +30,9 @@ internal interface IThemeProvider string ResourcePath { get; } /// - /// Creates acrylic backdrop parameters based on the provided theme context. + /// Creates backdrop parameters based on the provided theme context. /// - /// The current theme context, including theme, tint, and optional background details. - /// The computed for the backdrop. - AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context); + /// The current theme context, including theme, tint, transparency mode, and optional background details. + /// The computed for the backdrop. + BackdropParameters GetBackdropParameters(ThemeContext context); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/NormalThemeProvider.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/NormalThemeProvider.cs index c393894346..cee8aa86b2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/NormalThemeProvider.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/NormalThemeProvider.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.UI.Xaml; using Windows.UI; @@ -28,16 +29,28 @@ internal sealed class NormalThemeProvider : IThemeProvider public string ResourcePath => "ms-appx:///Styles/Theme.Normal.xaml"; - public AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context) + public BackdropParameters GetBackdropParameters(ThemeContext context) { var isLight = context.Theme == ElementTheme.Light || (context.Theme == ElementTheme.Default && _uiSettings.GetColorValue(UIColorType.Background).R > 128); - return new AcrylicBackdropParameters( + var backdropStyle = context.BackdropStyle ?? BackdropStyle.Acrylic; + var config = BackdropStyles.Get(backdropStyle); + + // Apply light/dark theme adjustment to luminosity + var baseLuminosityOpacity = isLight + ? config.BaseLuminosityOpacity + : Math.Min(config.BaseLuminosityOpacity + 0.06f, 1.0f); + + var effectiveOpacity = config.ComputeEffectiveOpacity(context.BackdropOpacity); + var effectiveLuminosityOpacity = baseLuminosityOpacity * context.BackdropOpacity; + + return new BackdropParameters( TintColor: isLight ? LightBaseColor : DarkBaseColor, FallbackColor: isLight ? LightBaseColor : DarkBaseColor, - TintOpacity: 0.5f, - LuminosityOpacity: isLight ? 0.9f : 0.96f); + EffectiveOpacity: effectiveOpacity, + EffectiveLuminosityOpacity: effectiveLuminosityOpacity, + Style: backdropStyle); } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeContext.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeContext.cs index 67432c8748..9862e6aa35 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeContext.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeContext.cs @@ -2,12 +2,16 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.CmdPal.UI.ViewModels; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Windows.UI; namespace Microsoft.CmdPal.UI.Services; +/// +/// Input parameters for theme computation, passed to theme providers. +/// internal sealed record ThemeContext { public ElementTheme Theme { get; init; } @@ -21,4 +25,8 @@ internal sealed record ThemeContext public double BackgroundImageOpacity { get; init; } public int? ColorIntensity { get; init; } + + public BackdropStyle? BackdropStyle { get; init; } + + public float BackdropOpacity { get; init; } = 1.0f; } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs index 65fbfb24d7..eb344780f0 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs @@ -72,10 +72,13 @@ internal sealed partial class ThemeService : IThemeService, IDisposable } // provider selection - var intensity = Math.Clamp(_settings.CustomThemeColorIntensity, 0, 100); - IThemeProvider provider = intensity > 0 && _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image - ? _colorfulThemeProvider - : _normalThemeProvider; + var themeColorIntensity = Math.Clamp(_settings.CustomThemeColorIntensity, 0, 100); + var imageTintIntensity = Math.Clamp(_settings.BackgroundImageTintIntensity, 0, 100); + var effectiveColorIntensity = _settings.ColorizationMode == ColorizationMode.Image + ? imageTintIntensity + : themeColorIntensity; + + IThemeProvider provider = UseColorfulProvider(effectiveColorIntensity) ? _colorfulThemeProvider : _normalThemeProvider; // Calculate values var tint = _settings.ColorizationMode switch @@ -96,32 +99,39 @@ internal sealed partial class ThemeService : IThemeService, IDisposable }; var opacity = Math.Clamp(_settings.BackgroundImageOpacity, 0, 100) / 100.0; - // create context and offload to actual theme provider + // create input and offload to actual theme provider var context = new ThemeContext { Tint = tint, - ColorIntensity = intensity, + ColorIntensity = effectiveColorIntensity, Theme = effectiveTheme, BackgroundImageSource = imageSource, BackgroundImageStretch = stretch, BackgroundImageOpacity = opacity, + BackdropStyle = _settings.BackdropStyle, + BackdropOpacity = Math.Clamp(_settings.BackdropOpacity, 0, 100) / 100f, }; - var backdrop = provider.GetAcrylicBackdrop(context); + var backdrop = provider.GetBackdropParameters(context); var blur = _settings.BackgroundImageBlurAmount; var brightness = _settings.BackgroundImageBrightness; // Create public snapshot (no provider!) + var hasColorization = effectiveColorIntensity > 0 + && _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image; + var snapshot = new ThemeSnapshot { Tint = tint, - TintIntensity = intensity / 100f, + TintIntensity = effectiveColorIntensity / 100f, Theme = effectiveTheme, BackgroundImageSource = imageSource, BackgroundImageStretch = stretch, BackgroundImageOpacity = opacity, BackdropParameters = backdrop, + BackdropOpacity = context.BackdropOpacity, BlurAmount = blur, BackgroundBrightness = brightness / 100f, + HasColorization = hasColorization, }; // Bundle with provider for internal use @@ -138,6 +148,12 @@ internal sealed partial class ThemeService : IThemeService, IDisposable ThemeChanged?.Invoke(this, new ThemeChangedEventArgs()); } + private bool UseColorfulProvider(int effectiveColorIntensity) + { + return _settings.ColorizationMode == ColorizationMode.Image + || (effectiveColorIntensity > 0 && _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor); + } + private static BitmapImage? LoadImageSafe(string? path) { if (string.IsNullOrWhiteSpace(path)) @@ -195,13 +211,15 @@ internal sealed partial class ThemeService : IThemeService, IDisposable { Tint = Colors.Transparent, Theme = ElementTheme.Light, - BackdropParameters = new AcrylicBackdropParameters(Colors.Black, Colors.Black, 0.5f, 0.5f), + BackdropParameters = new BackdropParameters(Colors.Black, Colors.Black, EffectiveOpacity: 0.5f, EffectiveLuminosityOpacity: 0.5f), + BackdropOpacity = 1.0f, BackgroundImageOpacity = 1, BackgroundImageSource = null, BackgroundImageStretch = Stretch.Fill, BlurAmount = 0, TintIntensity = 1.0f, BackgroundBrightness = 0, + HasColorization = false, }, Provider = _normalThemeProvider, }; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml index b9f31d8443..8a66b54d89 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml @@ -22,18 +22,50 @@ HorizontalAlignment="Stretch" Spacing="{StaticResource SettingsCardSpacing}"> - - - + + + + + + + + + @@ -62,19 +94,67 @@ + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + - + + + + - - + +