Better Dark Mode & Image Viewer Toggle Fixes#1965
Conversation
Overview This pull request introduces improved dark mode support by adding a tri-state theme cycle option to the tray menu, and fixes an issue with the Image Viewer background toggle affecting the global application theme. Changes: 1. Global Theme Tray Cycle: Added AppTheme option in SettingHelper and SetTheme method in App.xaml.cs; added tray menu item in TrayIconManager.cs to cycle Auto/Light/Dark with dynamic label. 2. Image Viewer Background Toggle Fix: Decoupled Image Viewer canvas background from global Theme; added CanvasTheme property and saved LastCanvasTheme; updated bindings in ImagePanel.xaml. Files modified: QuickLook/App.xaml.cs; QuickLook/TrayIconManager.cs; QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs; QuickLook.Plugin.ImageViewer/ImagePanel.xaml How to test: Run QuickLook, use tray menu to cycle theme, open image and toggle background; canvas background should change without affecting global UI.
Reviewer's GuideAdds a tri-state (Auto/Light/Dark) app theme setting wired through startup and a new tray menu item, and decouples the Image Viewer canvas background from the global theme by introducing a separate CanvasTheme with its own persisted setting. Sequence diagram for tray theme tri_state cyclingsequenceDiagram
actor User
participant TrayIconManager
participant SettingHelper
participant App
participant ThemeManager
participant OSThemeHelper
User->>TrayIconManager: click _itemThemeCycle RelayCommand
TrayIconManager->>SettingHelper: Get(AppTheme, 0, QuickLook)
TrayIconManager-->>TrayIconManager: next = (current + 1) % 3
TrayIconManager->>SettingHelper: Set(AppTheme, next, QuickLook)
TrayIconManager->>App: SetTheme(next)
App->>ThemeManager: Apply(ApplicationTheme.Light) [theme == 1]
App->>ThemeManager: Apply(ApplicationTheme.Dark) [theme == 2]
App->>OSThemeHelper: AppsUseDarkTheme() [theme == 0]
OSThemeHelper-->>App: appsUseDark
App->>ThemeManager: Apply(ApplicationTheme.Dark) [appsUseDark]
App->>ThemeManager: Apply(ApplicationTheme.Light) [!appsUseDark]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- Consider replacing the magic integer values for
AppTheme(0/1/2) with a strongly-typed enum or constants to make the theme switching logic self-documenting and less error-prone. - The tray theme cycle menu item currently uses hard-coded English headers (e.g.,
"Theme: Auto"); wiring these throughTranslationHelperlike the other menu items will keep the UI consistent and localizable. - The setting keys like
"AppTheme"and"LastCanvasTheme"are repeated as raw strings; centralizing them as shared constants would reduce the risk of typos and make future changes easier.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider replacing the magic integer values for `AppTheme` (0/1/2) with a strongly-typed enum or constants to make the theme switching logic self-documenting and less error-prone.
- The tray theme cycle menu item currently uses hard-coded English headers (e.g., `"Theme: Auto"`); wiring these through `TranslationHelper` like the other menu items will keep the UI consistent and localizable.
- The setting keys like `"AppTheme"` and `"LastCanvasTheme"` are repeated as raw strings; centralizing them as shared constants would reduce the risk of typos and make future changes easier.
## Individual Comments
### Comment 1
<location path="QuickLook/App.xaml.cs" line_range="231-235" />
<code_context>
- // Set initial theme based on system settings
- ThemeManager.Apply(OSThemeHelper.AppsUseDarkTheme() ? ApplicationTheme.Dark : ApplicationTheme.Light);
+ // Set initial theme based on system or user settings
+ var appTheme = SettingHelper.Get("AppTheme", 0, "QuickLook");
+ SetTheme(appTheme);
</code_context>
<issue_to_address>
**suggestion:** Consider using a strongly-typed enum (or wrapper) instead of raw integers for `AppTheme`.
Passing `AppTheme` as a raw `int` makes it easy to supply invalid values and hides the intent of each option. Defining a strongly-typed enum (e.g. `AppThemeSetting { Auto = 0, Light = 1, Dark = 2 }`) or a small wrapper would clarify the meaning of each value and prevent misuse, especially now that this setting is shared between App and `TrayIconManager`.
Suggested implementation:
```csharp
// Set initial theme based on system or user settings
var appThemeSetting = (AppThemeSetting)SettingHelper.Get("AppTheme", (int)AppThemeSetting.Auto, "QuickLook");
SetTheme(appThemeSetting);
```
To fully implement the strongly-typed `AppThemeSetting` suggestion, you will also need to:
1. Define the enum (in a shared location accessible by both `App` and `TrayIconManager`, e.g. a common settings or utilities namespace):
```csharp
public enum AppThemeSetting
{
Auto = 0,
Light = 1,
Dark = 2
}
```
2. Update the `SetTheme` method signature in `App.xaml.cs` (and its implementation) from something like:
```csharp
private void SetTheme(int appTheme)
```
to:
```csharp
private void SetTheme(AppThemeSetting appThemeSetting)
```
and internally map `AppThemeSetting` to `ApplicationTheme` / OS theme:
```csharp
private void SetTheme(AppThemeSetting appThemeSetting)
{
ApplicationTheme theme;
switch (appThemeSetting)
{
case AppThemeSetting.Light:
theme = ApplicationTheme.Light;
break;
case AppThemeSetting.Dark:
theme = ApplicationTheme.Dark;
break;
case AppThemeSetting.Auto:
default:
theme = OSThemeHelper.AppsUseDarkTheme()
? ApplicationTheme.Dark
: ApplicationTheme.Light;
break;
}
ThemeManager.Apply(theme);
}
```
3. Ensure all other callers (e.g. in `TrayIconManager` or any settings UI) use `AppThemeSetting` instead of raw integers when setting or reading the theme preference. When persisting, continue storing the underlying `int` value:
```csharp
SettingHelper.Set("AppTheme", (int)appThemeSetting, "QuickLook");
```
4. If `SettingHelper.Get` supports generics, consider adding a typed overload:
```csharp
public static TEnum GetEnum<TEnum>(string key, TEnum defaultValue, string section)
where TEnum : struct, Enum
{
var intValue = Get(key, Convert.ToInt32(defaultValue), section);
return Enum.IsDefined(typeof(TEnum), intValue)
? (TEnum)Enum.ToObject(typeof(TEnum), intValue)
: defaultValue;
}
```
and then replace the cast with:
```csharp
var appThemeSetting = SettingHelper.GetEnum("AppTheme", AppThemeSetting.Auto, "QuickLook");
```
</issue_to_address>
### Comment 2
<location path="QuickLook/TrayIconManager.cs" line_range="74-82" />
<code_context>
Header = TranslationHelper.Get("Icon_OpenDataFolder"),
Command = new RelayCommand(() => Process.Start("explorer.exe", SettingHelper.LocalDataPath)),
},
+ _itemThemeCycle = new TrayMenuItem()
+ {
+ Header = "Theme: Auto",
+ Command = new RelayCommand(() =>
+ {
+ var current = SettingHelper.Get("AppTheme", 0, "QuickLook");
+ var next = (current + 1) % 3;
+ SettingHelper.Set("AppTheme", next, "QuickLook");
+ App.SetTheme(next);
+ }),
+ },
</code_context>
<issue_to_address>
**suggestion:** Theme menu labels are hardcoded and not localized; consider centralizing label generation with translations.
The tray menu currently hardcodes English labels ("Theme: Auto/Light/Dark") in both the constructor and `TrayOnOpening`, bypassing `TranslationHelper` and duplicating label logic. Please centralize header generation in a helper (e.g., `UpdateThemeMenuHeader(int theme)`) that pulls from translations/resources, and call it from both sites to keep labels consistent and localized.
Suggested implementation:
```csharp
private readonly TrayMenuItem _itemAutorun = null!;
private readonly TrayMenuItem _itemCloseOnLostFocus = null!;
private readonly TrayMenuItem _itemThemeCycle = null!;
private void UpdateThemeMenuHeader(int theme)
{
string key;
switch (theme)
{
case 1:
key = "Tray_Theme_Light";
break;
case 2:
key = "Tray_Theme_Dark";
break;
default:
key = "Tray_Theme_Auto";
break;
}
_itemThemeCycle.Header = TranslationHelper.Get(key);
}
private TrayIconManager()
```
```csharp
var currentTheme = SettingHelper.Get("AppTheme", 0, "QuickLook");
_itemThemeCycle = new TrayMenuItem()
{
Header = string.Empty,
Command = new RelayCommand(() =>
{
var current = SettingHelper.Get("AppTheme", 0, "QuickLook");
var next = (current + 1) % 3;
SettingHelper.Set("AppTheme", next, "QuickLook");
App.SetTheme(next);
UpdateThemeMenuHeader(next);
}),
};
UpdateThemeMenuHeader(currentTheme);
```
1. Ensure you add the corresponding localized resources for the keys used in `UpdateThemeMenuHeader`:
- `Tray_Theme_Auto` (e.g., "Theme: Auto")
- `Tray_Theme_Light` (e.g., "Theme: Light")
- `Tray_Theme_Dark` (e.g., "Theme: Dark")
2. In `TrayOnOpening` (or any other place where the theme tray item header is updated), replace any hard-coded `"Theme: Auto"`, `"Theme: Light"`, or `"Theme: Dark"` strings with calls to `UpdateThemeMenuHeader`, e.g.:
```csharp
var currentTheme = SettingHelper.Get("AppTheme", 0, "QuickLook");
UpdateThemeMenuHeader(currentTheme);
```
This will centralize header generation and ensure both the constructor and `TrayOnOpening` use the same localized logic.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| _itemThemeCycle = new TrayMenuItem() | ||
| { | ||
| Header = "Theme: Auto", | ||
| Command = new RelayCommand(() => | ||
| { | ||
| var current = SettingHelper.Get("AppTheme", 0, "QuickLook"); | ||
| var next = (current + 1) % 3; | ||
| SettingHelper.Set("AppTheme", next, "QuickLook"); | ||
| App.SetTheme(next); |
There was a problem hiding this comment.
suggestion: Theme menu labels are hardcoded and not localized; consider centralizing label generation with translations.
The tray menu currently hardcodes English labels ("Theme: Auto/Light/Dark") in both the constructor and TrayOnOpening, bypassing TranslationHelper and duplicating label logic. Please centralize header generation in a helper (e.g., UpdateThemeMenuHeader(int theme)) that pulls from translations/resources, and call it from both sites to keep labels consistent and localized.
Suggested implementation:
private readonly TrayMenuItem _itemAutorun = null!;
private readonly TrayMenuItem _itemCloseOnLostFocus = null!;
private readonly TrayMenuItem _itemThemeCycle = null!;
private void UpdateThemeMenuHeader(int theme)
{
string key;
switch (theme)
{
case 1:
key = "Tray_Theme_Light";
break;
case 2:
key = "Tray_Theme_Dark";
break;
default:
key = "Tray_Theme_Auto";
break;
}
_itemThemeCycle.Header = TranslationHelper.Get(key);
}
private TrayIconManager() var currentTheme = SettingHelper.Get("AppTheme", 0, "QuickLook");
_itemThemeCycle = new TrayMenuItem()
{
Header = string.Empty,
Command = new RelayCommand(() =>
{
var current = SettingHelper.Get("AppTheme", 0, "QuickLook");
var next = (current + 1) % 3;
SettingHelper.Set("AppTheme", next, "QuickLook");
App.SetTheme(next);
UpdateThemeMenuHeader(next);
}),
};
UpdateThemeMenuHeader(currentTheme);-
Ensure you add the corresponding localized resources for the keys used in
UpdateThemeMenuHeader:Tray_Theme_Auto(e.g., "Theme: Auto")Tray_Theme_Light(e.g., "Theme: Light")Tray_Theme_Dark(e.g., "Theme: Dark")
-
In
TrayOnOpening(or any other place where the theme tray item header is updated), replace any hard-coded"Theme: Auto","Theme: Light", or"Theme: Dark"strings with calls toUpdateThemeMenuHeader, e.g.:var currentTheme = SettingHelper.Get("AppTheme", 0, "QuickLook"); UpdateThemeMenuHeader(currentTheme);
This will centralize header generation and ensure both the constructor and
TrayOnOpeninguse the same localized logic.
- Added strongly-typed AppThemeMode enum (Auto, Light, Dark) to replace magic integers - Created centralized setting keys (KeyAppTheme, KeyLastCanvasTheme) in SettingHelper for type-safe access - Wired tray menu text through TranslationHelper for full localization support with English fallbacks - Updated all theme and canvas theme references to use enum and constants throughout App.xaml.cs, TrayIconManager.cs, and ImagePanel.xaml.cs - Improved code clarity and maintainability by eliminating raw string literals and magic numbers
Overview
This pull request introduces improved dark mode support by adding a tri-state theme cycle option to the tray menu, and fixes an issue with the Image Viewer background toggle affecting the global application theme.
Changes:
Files modified: QuickLook/App.xaml.cs; QuickLook/TrayIconManager.cs; QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs; QuickLook.Plugin.ImageViewer/ImagePanel.xaml
How to test: Run QuickLook, use tray menu to cycle theme, open image and toggle background; canvas background should change without affecting global UI.
PR Checklist
explorer_Qui64mXdgo.mp4
To put it simply no one should have to go through this (the constant white flashes) at 3am in the morning 😭
Related Issue (if any)
Please provide related issue numbers:
Additional Notes
Add any extra notes here:
Summary by Sourcery
Introduce a tray theme cycle and decouple the Image Viewer canvas background from the global application theme.
New Features:
Bug Fixes:
Enhancements: