Skip to content

CmdPal: Light, dark, pink, and unicorns#43505

Merged
michaeljolley merged 21 commits intomicrosoft:mainfrom
jiripolasek:feature/38444-cmdpal-with-colors-and-unicorns
Dec 10, 2025
Merged

CmdPal: Light, dark, pink, and unicorns#43505
michaeljolley merged 21 commits intomicrosoft:mainfrom
jiripolasek:feature/38444-cmdpal-with-colors-and-unicorns

Conversation

@jiripolasek
Copy link
Copy Markdown
Collaborator

@jiripolasek jiripolasek commented Nov 12, 2025

Summary of the Pull Request

This PR introduces user settings for app mode themes (dark, light, or system) and background customization options, including custom colors, system accent colors, or custom images.

  • Adds a new page to the Settings window with new appearance settings and moves some existing settings there as well.
  • Introduces a new core-level service abstraction, IThemeService, that holds the state for the current theme.
  • Uses the helper class ResourceSwapper to update application-level XAML resources. The way WinUI / XAML handles these is painful, and XAML Hot Reload is pain². Initialization must be lazy, as XAML resources can only be accessed after the window is activated.
  • ThemeService takes app and system settings and selects one of the registered IThemeProviders to calculate visuals and choose the appropriate XAML resources.
    • At the moment, there are two:
      • NormalThemeProvider
        • Provides the current uncolorized light and dark styles
        • ms-appx:///Styles/Theme.Normal.xaml
      • ColorfulThemeProvider
        • Style that matches the Windows 11 visual style (based on the Start menu) and colors
        • ms-appx:///Styles/Theme.Colorful.xaml
        • Applied when the background is colorized or a background image is selected
  • The app theme is applied only on the main window (WindowThemeSynchronizer helper class can be used to synchronize other windows if needed).
  • Adds a new dependency on Microsoft.Graphics.Win2D.
  • Adds a custom color picker popup; the one from the Community Toolkit occasionally loses the selected color.
  • Flyby: separates the keyword tag and localizable label for pages in the Settings window navigation.

Pictures? Pictures!

image image

Matching Windows accent color and tint:

image

PR Checklist

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

@jiripolasek jiripolasek added the Product-Command Palette Refers to the Command Palette utility label Nov 12, 2025
@niels9001
Copy link
Copy Markdown
Collaborator

Tried it out, loving it :)!

One question to consider is whether to create a separate settings card for the background image or simply add it as another background option alongside the colors.

Yeah, this came to mind when I was playing with the settings - I wonder if we should combine this with the color combobox? Depending on the selection we'd show the right settings?

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

jiripolasek commented Nov 21, 2025

Tried it out, loving it :)!

One question to consider is whether to create a separate settings card for the background image or simply add it as another background option alongside the colors.

Yeah, this came to mind when I was playing with the settings - I wonder if we should combine this with the color combobox? Depending on the selection we'd show the right settings?

@niels9001 Here's a branch that combines them, plus adds blur and brightness controls along with improved tint adjustment for the background image. I planned to include this later, but added it now to give us a better perspective on what needs to be in UI.

Moving pictures included inside: jiripolasek#15

Edit: merged

…fects (blur, brightness, tint) directly to the image control using composition.

Combines Background Colors and Background Image into a single expander.
The sub-controls have changed slightly: previously, the custom color drop-down was always visible, and selecting it would automatically switch to custom color mode. However, after merging the image controls into it, the process no longer feels intuitive.
Uses the Visual layer and Win2D effects for image processing. Introduces a new custom control that encapsulates Visual layer effects applied to an image. It chains the following effects and provides user control over them:
Brightness – makes the image brighter or darker
Blur – softens the image to reduce contrast and fine details
Tint – tint color and intensity are now applied as a blended effect on top of the previous ones
@jiripolasek jiripolasek marked this pull request as ready for review November 24, 2025 17:08
@jiripolasek jiripolasek requested a review from a team as a code owner November 24, 2025 17:08
@jiripolasek
Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Collaborator

@niels9001 niels9001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AMAZING WORK!!! 🔥🔥🔥

@michaeljolley
Copy link
Copy Markdown
Contributor

@jiripolasek, I can't get this to build locally. Not sure what's going on.

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

@michaeljolley: on it

@michaeljolley
Copy link
Copy Markdown
Contributor

@michaeljolley: on it

Disregard. It's on my end.

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

@michaeljolley: on it

Disregard. It's on my end.

A trained squad of squirrels went nuts and managed to resolve the merge conflict 🐿️🐿️🐿️

@github-actions

This comment has been minimized.

@michaeljolley
Copy link
Copy Markdown
Contributor

Any ideas why it freezes when I click Browse for the background image? No explorer or file picker appears.

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

Any ideas why it freezes when I click Browse for the background image? No explorer or file picker appears.

Nope, that’s never happened to me so far. What configuration are you using -- Debug or Release -- and is the debugger attached?

@michaeljolley
Copy link
Copy Markdown
Contributor

Any ideas why it freezes when I click Browse for the background image? No explorer or file picker appears.

Nope, that’s never happened to me so far. What configuration are you using -- Debug or Release -- and is the debugger attached?

In debug. Never gets a return from var file = await picker.PickSingleFileAsync()!; and no picker appears.

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

Any ideas why it freezes when I click Browse for the background image? No explorer or file picker appears.

Nope, that’s never happened to me so far. What configuration are you using -- Debug or Release -- and is the debugger attached?

In debug. Never gets a return from var file = await picker.PickSingleFileAsync()!; and no picker appears.

Thanks. I can’t replicate the issue, so I’ll need to investigate further.
Could you try copying that method into a new WinUI 3 app, so we can exclude a local environment?

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

jiripolasek commented Dec 1, 2025

@michaeljolley So far now luck. I can't replicate the problem with picker anywhere :(

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

/azp run

Weill be added later, stay tuned
Copy link
Copy Markdown
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay 58/58. Couple last comments.

If you don't want to do the move out of Core thing, then I can as a follow-up

try
{
rect.Fill = brush;
rect.CompositeMode = ElementCompositeMode.SourceOver;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u wot m8

you just hijacked the cursor rectangle from the textbox

you're telling me we could have done this all along

we spent a week trying to figure out how to add just enough shading to the background of cmdpal, so that the cursor would show up on HDR displays

and you just

hijacked the cursor

/cc @niels9001 cause this will make him cry

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also great work

@github-actions

This comment has been minimized.

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

okay 58/58. Couple last comments.

If you don't want to do the move out of Core thing, then I can as a follow-up

@zadjii-msft Actually, I want to, with extreme pleasure. Pushed. It's a bit rough, I've got to properly re-test everything a bit more, and some things could be smoothed out, but I think it's stable enough to go ahead with the review.

Copy link
Copy Markdown
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

@zadjii-msft Can we add Microsoft.Graphics.Win2D to PowerToysPublicDependencies feed? It looked like it's there when I tried to query it from there, but CI can't see it.

I think I’ve covered all the notes, and the last step is to merge in the main, just to be sure

@DHowett
Copy link
Copy Markdown
Member

DHowett commented Dec 9, 2025

@jiripolasek done! all versions 1.3.0 to 1.3.2 are now in-feed

@DHowett
Copy link
Copy Markdown
Member

DHowett commented Dec 9, 2025

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jiripolasek
Copy link
Copy Markdown
Collaborator Author

@jiripolasek done! all versions 1.3.0 to 1.3.2 are now in-feed

@DHowett Many thanks!

image

@michaeljolley michaeljolley merged commit 97c1de8 into microsoft:main Dec 10, 2025
10 checks passed
@jiripolasek
Copy link
Copy Markdown
Collaborator Author

image

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive theme customization to CmdPal, including dark/light/system theme modes, custom background colors (including system accent), background images with blur/brightness controls, and color tinting. The implementation introduces a service-based architecture with IThemeService, multiple theme providers, and dynamic resource swapping.

Changes:

  • New Appearance settings page with live preview of customizations
  • Theme service architecture with Normal and Colorful theme providers
  • Background image support with blur, brightness, and tint effects using Win2D
  • Custom color picker controls and system accent color integration

Reviewed changes

Copilot reviewed 57 out of 58 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
AppearancePage.xaml/.cs New settings page for theme customization
ThemeService.cs Core service managing theme state and provider selection
ResourceSwapper.cs Dynamic XAML resource dictionary switching
BlurImageControl.cs Composition-based image effects control
WallpaperHelper.cs COM interop for desktop wallpaper access
ColorExtensions.cs HSV color manipulation utilities
Theme.Normal.xaml / Theme.Colorful.xaml Theme resource dictionaries
MainWindow.xaml/.cs Integration of theme service and background rendering
SettingsModel.cs New theme-related settings properties
AppearanceSettingsViewModel.cs ViewModel for appearance settings
Files not reviewed (1)
  • src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs: Language not supported

Comment on lines +175 to +184
public int ColorIntensity
{
get => _settings.CustomThemeColorIntensity;
set
{
_settings.CustomThemeColorIntensity = value;
OnPropertyChanged();
Save();
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ColorIntensity setter updates the property value but doesn't validate the range before setting. This could allow invalid values to be stored. Consider clamping the value to the valid range [0, 100] before assignment, similar to how other properties handle bounds checking.

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +397
private void LoadImageAsync(ImageSource imageSource)
{
try
{
if (imageSource is Microsoft.UI.Xaml.Media.Imaging.BitmapImage bitmapImage)
{
_imageBrush ??= _compositor?.CreateSurfaceBrush();
if (_imageBrush is null)
{
return;
}

var loadedSurface = LoadedImageSurface.StartLoadFromUri(bitmapImage.UriSource);
loadedSurface.LoadCompleted += (_, _) =>
{
if (_imageBrush is not null)
{
_imageBrush.Surface = loadedSurface;
_imageBrush.Stretch = ConvertStretch(ImageStretch);
_imageBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.Linear;
}
};

_effectBrush?.SetSourceParameter(ImageSourceParameterName, _imageBrush);
}
}
catch (Exception ex)
{
Logger.LogError("Failed to load image for BlurImageControl: {0}", ex);
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async method LoadImageAsync doesn't use the async/await pattern correctly. The method name suggests it's async, but it doesn't return a Task and doesn't use await. The LoadCompleted event handler runs on a callback thread that may not be the UI thread. Consider renaming to LoadImage or ensuring proper thread marshaling for the brush update.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +39
internal sealed partial class WallpaperHelper
{
private readonly IDesktopWallpaper? _desktopWallpaper;

public WallpaperHelper()
{
try
{
var desktopWallpaper = ComHelper.CreateComInstance<IDesktopWallpaper>(
ref Unsafe.AsRef(in CLSID.DesktopWallpaper),
CLSCTX.ALL);

_desktopWallpaper = desktopWallpaper;
}
catch (Exception ex)
{
// If COM initialization fails, keep helper usable with safe fallbacks
Logger.LogError("Failed to initialize DesktopWallpaper COM interface", ex);
_desktopWallpaper = null;
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WallpaperHelper constructor initializes a COM interface but doesn't implement IDisposable to clean up the COM reference. This could lead to COM object leaks. The _desktopWallpaper field should be released when no longer needed.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +32
public ScreenPreview()
{
InitializeComponent();

var wallpaperHelper = new WallpaperHelper();
WallpaperImage!.Source = wallpaperHelper.GetWallpaperImage()!;
ScreenBorder!.Background = new SolidColorBrush(wallpaperHelper.GetWallpaperColor());
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ScreenPreview constructor accesses WallpaperImage and ScreenBorder with null-forgiving operator (!) but doesn't verify they're non-null before use. If InitializeComponent fails to create these elements, this will throw a NullReferenceException. Add null checks before accessing these properties.

Copilot uses AI. Check for mistakes.

public int CustomThemeColorIntensity { get; set; } = 100;

public int BackgroundImageOpacity { get; set; } = 20;
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BackgroundImageOpacity default value is set to 20, which makes background images very transparent by default. This seems inconsistent with the typical user expectation that an opacity setting would default to 100 (fully opaque). Consider changing the default to 100 or adding a comment explaining this design decision.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

In for .97 Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CmdPal: Add an Option To toggle Dark Mode

7 participants