CmdPal: Add slideshow background mode and rotation settings#46452
CmdPal: Add slideshow background mode and rotation settings#46452Error0229 wants to merge 5 commits intomicrosoft:mainfrom
Conversation
…t#45879) Add a dedicated Slideshow background mode in CmdPal appearance settings with folder-based image rotation, configurable interval, and shuffle. - Add ColorizationMode.Slideshow enum value - Add slideshow folder path, change interval, and shuffle settings - Add slideshow rotation engine in ThemeService (sequential/shuffle, interval-based, rotate-on-hide so next activation shows new image) - Add BackgroundImagePathResolver utility with unit tests (13 tests) - Add folder picker and slideshow controls to AppearancePage - Fix settings crash on empty preview image paths - Add localized strings for all new UI elements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single-button StackPanel wrappers around the image picker and folder picker buttons serve no purpose. Use the Button directly as the SettingsCard content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eImageUri Move the relative-path-to-absolute-URI logic out of the view model ternary chains into a dedicated helper. Both AppearanceSettingsViewModel and DockAppearanceSettingsViewModel now read as a simple null-check chain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@microsoft-github-policy-service agree |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx
Fixed
Show fixed
Hide fixed
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IThemeService.cs
Fixed
Show fixed
Hide fixed
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f715528 to
1d6734e
Compare
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Adds a new Slideshow background mode to CmdPal’s appearance settings, including folder selection and rotation behavior, so users can rotate background images from a directory instead of a single file.
Changes:
- Introduces
ColorizationMode.Slideshowplus new persisted settings for slideshow folder, change interval, and shuffle. - Updates settings UI to support slideshow selection (folder picker + interval/shuffle controls).
- Adds
BackgroundImagePathResolverutility (with unit tests) and wires it into theme/image preview paths.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ColorizationMode.cs | Adds Slideshow mode to the colorization enum. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs | Persists slideshow folder, interval minutes, and shuffle flag. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IThemeService.cs | Adds RefreshThemeForActivation() hook for summon-time refresh behavior. |
| src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs | Implements slideshow rotation/selection logic and integrates slideshow into theme snapshot generation. |
| src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs | Triggers slideshow refresh before hiding to avoid visible swaps on next activation. |
| src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml | Adds Slideshow option and slideshow-specific settings cards/controls. |
| src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml.cs | Implements folder picker and forces mode switch when picking file/folder. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs | Adds slideshow settings bindings and preview image resolution logic. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/DockAppearanceSettingsViewModel.cs | Uses resolver-based URI creation for safer image preview resolution. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs | Applies theme snapshots on UI thread without always dispatching. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackgroundImagePathResolver.cs | New helper to resolve folder-vs-file paths, supported images, and safe image URIs. |
| src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/BackgroundImagePathResolverTests.cs | Adds unit tests covering resolver behaviors (folder/file/URI cases). |
| src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw | Adds localized strings for slideshow UI and interval/shuffle labels. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx | Adds localized string used by folder picker commit button. |
| src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs | Regenerates strongly-typed accessor for the new resx string. |
| .github/actions/spell-check/expect.txt | Adds “slideshow” to spelling allow-list and removes an unused entry. |
Files not reviewed (1)
- src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs: Language not supported
| return; | ||
| } | ||
|
|
||
| Interlocked.Exchange(ref _rotateMainBackgroundOnActivation, 1); |
There was a problem hiding this comment.
RefreshThemeForActivation() calls Reload() directly. Since Reload creates BitmapImage and swaps XAML resources, this should be marshaled onto the UI DispatcherQueue (or at least guard with _dispatcherQueue.HasThreadAccess). Otherwise any non-UI-thread caller of IThemeService.RefreshThemeForActivation could trigger thread-affinity crashes or undefined behavior.
| Interlocked.Exchange(ref _rotateMainBackgroundOnActivation, 1); | |
| Interlocked.Exchange(ref _rotateMainBackgroundOnActivation, 1); | |
| // Ensure Reload runs on the UI thread, since it manipulates XAML resources and BitmapImage. | |
| if (_dispatcherQueue is not null && !_dispatcherQueue.HasThreadAccess) | |
| { | |
| _dispatcherQueue.TryEnqueue(Reload); | |
| return; | |
| } |
There was a problem hiding this comment.
All current callers of RefreshThemeForActivation() run on the UI thread — HideWindow() is a UI event
handler, Initialize() runs during startup on UI thread, and RequestReload() fires via a DispatcherQueueTimer. Not a bug today, but the IThemeService interface doesn't document thread affinity. Happy to add a Debug.Assert(_dispatcherQueue.HasThreadAccess) guard or XML doc comment if the team thinks it's warranted.
| var files = BackgroundImagePathResolver.GetSupportedImageFiles(folderPath); | ||
| if (files.Count == 0) | ||
| { | ||
| ResetMainBackgroundImageState(); | ||
| return null; |
There was a problem hiding this comment.
ResolveMainBackgroundImagePath enumerates and sorts the entire slideshow folder (GetSupportedImageFiles) during Reload(), which runs on the UI thread. With large folders or slow storage this can stall hide/summon and other theme reloads; consider caching the file list (and only refreshing when the folder changes or a rescan is needed) or moving the scan off the UI thread and applying the next image once available.
There was a problem hiding this comment.
Acknowledged — GetSupportedImageFiles does synchronous EnumerateFiles + OrderBy + ToArray on the UI thread. For typical wallpaper folders (10-100 images) this is sub-millisecond on SSD. Adding caching would introduce invalidation complexity (file adds/deletes in the folder). Happy to add caching if the team considers it necessary
| private string? GetEffectiveBackgroundPreviewPath() | ||
| { | ||
| return ColorizationMode switch | ||
| { | ||
| ColorizationMode.Slideshow => BackgroundImagePathResolver.ResolvePreviewImagePath(BackgroundImageSlideshowFolderPath), | ||
| _ => BackgroundImagePath, | ||
| }; |
There was a problem hiding this comment.
GetEffectiveBackgroundPreviewPath() calls BackgroundImagePathResolver.ResolvePreviewImagePath(), which performs directory enumeration/sorting for folder paths. Because EffectiveBackgroundImageSource is a computed property used by bindings, this can introduce repeated synchronous disk I/O on the UI thread; consider resolving/caching the preview image path when BackgroundImageSlideshowFolderPath changes (and invalidating it when needed) rather than doing filesystem work in the getter path.
| private string? GetEffectiveBackgroundPreviewPath() | |
| { | |
| return ColorizationMode switch | |
| { | |
| ColorizationMode.Slideshow => BackgroundImagePathResolver.ResolvePreviewImagePath(BackgroundImageSlideshowFolderPath), | |
| _ => BackgroundImagePath, | |
| }; | |
| // Cache for slideshow preview resolution to avoid repeated directory enumeration on the UI thread. | |
| private string? _cachedSlideshowPreviewPath; | |
| private string? _cachedSlideshowFolderPathForPreview; | |
| private string? GetEffectiveBackgroundPreviewPath() | |
| { | |
| if (ColorizationMode == ColorizationMode.Slideshow) | |
| { | |
| var currentFolderPath = BackgroundImageSlideshowFolderPath; | |
| if (string.Equals(currentFolderPath, _cachedSlideshowFolderPathForPreview, StringComparison.Ordinal) | |
| && _cachedSlideshowPreviewPath is not null) | |
| { | |
| return _cachedSlideshowPreviewPath; | |
| } | |
| var resolvedPath = BackgroundImagePathResolver.ResolvePreviewImagePath(currentFolderPath); | |
| _cachedSlideshowFolderPathForPreview = currentFolderPath; | |
| _cachedSlideshowPreviewPath = resolvedPath; | |
| return resolvedPath; | |
| } | |
| return BackgroundImagePath; |
There was a problem hiding this comment.
This getter only runs while the settings page is visible, and ResolvePreviewImagePath picks the first file
from a typically small folder — the real-world cost is negligible. Leaving it uncached to keep behavior
correct and code simple
…trings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary of the Pull Request
PR Checklist
Detailed Description of the Pull Request / Additional comments
New mode: Slideshow
ColorizationMode.Slideshowis added alongside the existingImagemode. Both modes share tint/opacity/blur/fit controls, but have separate path controls — single-image uses a file picker (BackgroundImagePath), slideshow uses a folder picker (BackgroundImageSlideshowFolderPath).Rotation engine
ThemeServicemaintains internal state to track the current slideshow image. Rotation triggers on: folder changed, current file deleted, or activation after the configured interval elapsed. Supports both shuffle and sequential (alphabetical wrap-around) ordering.Shuffle algorithm
Uses Fisher-Yates to pre-shuffle the full index array, then walks through it with a cursor. When the deck is exhausted, reshuffles — with the last-shown image swapped away from position 0 to avoid repeats at deck boundaries. This guarantees every image is shown exactly once before any image repeats.
Rotation timing
MainWindow.HideWindow()callsRefreshThemeForActivation()before hiding, so the image swaps while invisible — no flicker on next activation.Settings
New JSON keys (additive, no migration needed):
BackgroundImageSlideshowFolderPath(string)BackgroundImageChangeIntervalMinutes(int, default 0 = on activation only)BackgroundImageShuffle(bool, default true)Validation Steps Performed
src/modules/cmdpal/Microsoft.CmdPal.UIwithtools/build/build.cmd(Debug/x64) — 0 errorsBackgroundImagePathResolverTests(13/13 passed)