Skip to content

CmdPal: Add slideshow background mode and rotation settings#46452

Open
Error0229 wants to merge 5 commits intomicrosoft:mainfrom
Error0229:feature/cmdpal-background-slideshow-45879
Open

CmdPal: Add slideshow background mode and rotation settings#46452
Error0229 wants to merge 5 commits intomicrosoft:mainfrom
Error0229:feature/cmdpal-background-slideshow-45879

Conversation

@Error0229
Copy link

@Error0229 Error0229 commented Mar 24, 2026

Summary of the Pull Request

  • Add a dedicated Slideshow background mode in CmdPal appearance settings
  • Add slideshow folder path, change interval (on activation / 1 / 10 / 30 / 60 min), and shuffle settings
  • Add slideshow rotation engine in ThemeService — rotates on hide so next activation shows new image
  • Shuffle uses Fisher-Yates algorithm to guarantee every image is shown before any repeats
  • Add BackgroundImagePathResolver utility with 13 unit tests
  • Fix a settings crash by guarding against empty preview image paths

PR Checklist

Detailed Description of the Pull Request / Additional comments

New mode: Slideshow

ColorizationMode.Slideshow is added alongside the existing Image mode. 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

ThemeService maintains 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() calls RefreshThemeForActivation() 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

  • Built src/modules/cmdpal/Microsoft.CmdPal.UI with tools/build/build.cmd (Debug/x64) — 0 errors
  • Built and ran BackgroundImagePathResolverTests (13/13 passed)
  • Manually validated CmdPal settings flow for Image vs Slideshow modes
  • Validated on-hide rotation behavior
  • Verified shuffle covers all images before repeating

catol-abacus and others added 3 commits March 23, 2026 17:13
…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>
@Error0229
Copy link
Author

@microsoft-github-policy-service agree

@github-actions

This comment has been minimized.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Error0229 Error0229 force-pushed the feature/cmdpal-background-slideshow-45879 branch from f715528 to 1d6734e Compare March 24, 2026 01:18
@michaeljolley
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@michaeljolley michaeljolley added the Product-Command Palette Refers to the Command Palette utility label Mar 24, 2026
@michaeljolley michaeljolley requested a review from Copilot March 24, 2026 18:40
Copy link
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

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.Slideshow plus new persisted settings for slideshow folder, change interval, and shuffle.
  • Updates settings UI to support slideshow selection (folder picker + interval/shuffle controls).
  • Adds BackgroundImagePathResolver utility (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);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
Copy link
Author

@Error0229 Error0229 Mar 25, 2026

Choose a reason for hiding this comment

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

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.

Comment on lines +262 to +266
var files = BackgroundImagePathResolver.GetSupportedImageFiles(folderPath);
if (files.Count == 0)
{
ResetMainBackgroundImageState();
return null;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

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

Comment on lines +524 to +530
private string? GetEffectiveBackgroundPreviewPath()
{
return ColorizationMode switch
{
ColorizationMode.Slideshow => BackgroundImagePathResolver.ResolvePreviewImagePath(BackgroundImageSlideshowFolderPath),
_ => BackgroundImagePath,
};
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
Copy link
Author

@Error0229 Error0229 Mar 25, 2026

Choose a reason for hiding this comment

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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CmdPal: Support background image rotation from a folder

4 participants