CmdPal: Light, dark, pink, and unicorns#43505
Conversation
|
Tried it out, loving it :)!
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
src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
Outdated
Show resolved
Hide resolved
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@jiripolasek, I can't get this to build locally. Not sure what's going on. |
|
@michaeljolley: on it |
Disregard. It's on my end. |
A trained squad of squirrels went nuts and managed to resolve the merge conflict 🐿️🐿️🐿️ |
This comment has been minimized.
This comment has been minimized.
|
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 |
Thanks. I can’t replicate the issue, so I’ll need to investigate further. |
|
@michaeljolley So far now luck. I can't replicate the problem with |
|
/azp run |
Weill be added later, stay tuned
zadjii-msft
left a comment
There was a problem hiding this comment.
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
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ColorPickerButton.xaml.cs
Show resolved
Hide resolved
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/ColorExtensions.cs
Outdated
Show resolved
Hide resolved
| try | ||
| { | ||
| rect.Fill = brush; | ||
| rect.CompositeMode = ElementCompositeMode.SourceOver; |
There was a problem hiding this comment.
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
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/VisualTreeExtensions.cs
Outdated
Show resolved
Hide resolved
src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs
Outdated
Show resolved
Hide resolved
src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ColorfulThemeProvider.cs
Outdated
Show resolved
Hide resolved
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs
Fixed
Show fixed
Hide fixed
This comment has been minimized.
This comment has been minimized.
@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. |
|
@zadjii-msft Can we add I think I’ve covered all the notes, and the last step is to merge in the main, just to be sure |
|
@jiripolasek done! all versions 1.3.0 to 1.3.2 are now in-feed |
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
@DHowett Many thanks!
|
There was a problem hiding this comment.
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
| public int ColorIntensity | ||
| { | ||
| get => _settings.CustomThemeColorIntensity; | ||
| set | ||
| { | ||
| _settings.CustomThemeColorIntensity = value; | ||
| OnPropertyChanged(); | ||
| Save(); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| public ScreenPreview() | ||
| { | ||
| InitializeComponent(); | ||
|
|
||
| var wallpaperHelper = new WallpaperHelper(); | ||
| WallpaperImage!.Source = wallpaperHelper.GetWallpaperImage()!; | ||
| ScreenBorder!.Background = new SolidColorBrush(wallpaperHelper.GetWallpaperColor()); | ||
| } |
There was a problem hiding this comment.
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.
|
|
||
| public int CustomThemeColorIntensity { get; set; } = 100; | ||
|
|
||
| public int BackgroundImageOpacity { get; set; } = 20; |
There was a problem hiding this comment.
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.


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.
IThemeService, that holds the state for the current theme.ResourceSwapperto 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.ThemeServicetakes app and system settings and selects one of the registeredIThemeProviders to calculate visuals and choose the appropriate XAML resources.NormalThemeProviderms-appx:///Styles/Theme.Normal.xamlColorfulThemeProviderms-appx:///Styles/Theme.Colorful.xamlWindowThemeSynchronizerhelper class can be used to synchronize other windows if needed).Microsoft.Graphics.Win2D.Pictures? Pictures!
Matching Windows accent color and tint:
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed