Appearance
Spec: Dark/Light Auto-Theme
Overview
Automatically switch between dark and light themes based on the Windows system theme preference, with smooth transitions and per-element theme awareness for the circuit canvas.
Motivation
- Windows 10/11 users commonly switch between dark and light modes.
- Many users enable scheduled dark mode (dark at night, light during day).
- The app currently has multiple themes but requires manual switching.
- Automatic theme-following is expected behavior for modern Windows apps.
Requirements
Functional Requirements
System Theme Detection: Detect current Windows dark/light mode setting.
Auto-Follow Mode: Option to automatically match system theme changes.
Real-Time Switching: Theme changes immediately when system theme changes (no app restart required).
Smooth Transition: Animate color transitions over ~200ms when switching.
Canvas Theme Adaptation: Canvas background, grid, and wire colors adapt.
Component Rendering: Component shapes render appropriately in both themes.
Theme Preference Options: Auto (follow system), Always Light, Always Dark, or Custom (user-defined theme).
Per-Monitor Theme: If system supports per-app theme, respect it.
Accent Color Integration: Use Windows accent color for highlights/selection.
Scheduled Switching: Optional built-in schedule (dark after sunset) independent of system setting.
Theme-Aware Exports: Exported images use a consistent theme (configurable) regardless of current app theme.
Signal Colors in Dark Mode: Ensure High/Low/Floating signal colors remain distinguishable and accessible in both themes.
Non-Functional Requirements
- Theme switch completes in < 300ms (including canvas re-render).
- No flickering or partial-theme states during transition.
- All 5 existing custom themes continue to work (auto-theme is additive).
- Dark mode colors meet WCAG 2.1 AA contrast requirements.
- Memory: no duplicate resource allocation for unused theme.
Design
System Theme Detection (WinUI 3)
csharp
public class ThemeWatcher : IDisposable
{
private readonly UISettings _uiSettings = new();
public ThemeWatcher()
{
_uiSettings.ColorValuesChanged += OnSystemThemeChanged;
}
public AppTheme CurrentSystemTheme
{
get
{
var foreground = _uiSettings.GetColorValue(UIColorType.Foreground);
// If foreground is light, system is in dark mode
return IsColorLight(foreground) ? AppTheme.Dark : AppTheme.Light;
}
}
private void OnSystemThemeChanged(UISettings sender, object args)
{
DispatcherQueue.TryEnqueue(() =>
{
if (ThemePreference == ThemePreference.Auto)
ApplyTheme(CurrentSystemTheme);
});
}
}Theme Resource Structure
csharp
public class ThemeColors
{
// Canvas
public Color CanvasBackground { get; set; }
public Color GridLines { get; set; }
public Color GridDots { get; set; }
// Components
public Color ComponentBody { get; set; }
public Color ComponentBorder { get; set; }
public Color ComponentLabel { get; set; }
public Color PinColor { get; set; }
// Wires
public Color WireDefault { get; set; }
public Color WireHigh { get; set; }
public Color WireLow { get; set; }
public Color WireFloating { get; set; }
// Selection
public Color SelectionHighlight { get; set; }
public Color SelectionBox { get; set; }
// UI Chrome
public Color Foreground { get; set; }
public Color Background { get; set; }
public Color AccentColor { get; set; }
}Transition Animation
csharp
public async Task TransitionTheme(ThemeColors from, ThemeColors to, TimeSpan duration)
{
var animation = new CompositionAnimation(duration);
// Interpolate all color properties over the duration
foreach (var property in typeof(ThemeColors).GetProperties())
{
var fromColor = (Color)property.GetValue(from);
var toColor = (Color)property.GetValue(to);
animation.AddKeyFrame(property.Name, fromColor, toColor);
}
await animation.StartAsync();
}Settings Integration
Theme Preference:
○ Auto (follow Windows) ← default
○ Light
○ Dark
○ Custom: [Select theme ▼]
[ ] Animate transitions
[ ] Use Windows accent color
[ ] Schedule: Dark after [18:00] until [06:00]Implementation Tasks
- Implement
ThemeWatcherto detect system dark/light mode changes. - Register for
UISettings.ColorValuesChangedevent. - Define
ThemeColorsfor light and dark canvas rendering. - Update canvas renderer to use theme-reactive color bindings.
- Implement smooth color transition animation.
- Add "Auto" option to existing theme preferences.
- Integrate Windows accent color for selection/highlights.
- Ensure all signal colors meet contrast requirements in both modes.
- Update component shape rendering for dark/light variants.
- Add optional scheduled theme switching (time-based).
- Update export to use configurable theme (not current app theme).
- Test transitions between all theme combinations.
Risks & Open Questions
- Should custom themes also have a "dark variant" auto-generated?
- How to handle third-party/plugin UI elements that don't adapt?
- Performance of re-rendering entire canvas on theme change for large circuits?
- Should the minimap also transition or switch instantly?
- Accent color: use for selection only, or also for active signals?
Priority
Low — Nice polish feature; existing manual theme switching is adequate.