diff --git a/src/CodingWithCalvin.VSToolbox/App.xaml b/src/CodingWithCalvin.VSToolbox/App.xaml
index 52d1542..27e1ce2 100644
--- a/src/CodingWithCalvin.VSToolbox/App.xaml
+++ b/src/CodingWithCalvin.VSToolbox/App.xaml
@@ -14,6 +14,12 @@
+
+
+
+
+
+
diff --git a/src/CodingWithCalvin.VSToolbox/App.xaml.cs b/src/CodingWithCalvin.VSToolbox/App.xaml.cs
index 50fab76..5e792b7 100644
--- a/src/CodingWithCalvin.VSToolbox/App.xaml.cs
+++ b/src/CodingWithCalvin.VSToolbox/App.xaml.cs
@@ -65,8 +65,10 @@ public partial class App : Application
private Window? _window;
private AppWindow? _appWindow;
private TrayIconService? _trayIconService;
+ private SettingsService? _settingsService;
public Window? MainWindow => _window;
+ public SettingsService Settings => _settingsService ??= new SettingsService();
public App()
{
@@ -122,7 +124,11 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
// Set window size and position to bottom-right
PositionWindowBottomRight(540, 600);
- _window.Activate();
+ // Only show window if not starting minimized
+ if (!Settings.StartMinimized)
+ {
+ _window.Activate();
+ }
}
private void ConfigureCustomTitleBar()
@@ -178,11 +184,21 @@ private void PositionWindowBottomRight(int width, int height)
private void OnAppWindowClosing(AppWindow sender, AppWindowClosingEventArgs args)
{
- // Prevent window from closing - hide it instead
- args.Cancel = true;
+ if (Settings.CloseToTray)
+ {
+ // Prevent window from closing - hide it instead
+ args.Cancel = true;
- // Hide the window
- _appWindow?.Hide();
+ // Hide the window
+ _appWindow?.Hide();
+ }
+ else
+ {
+ // Actually close the app
+ _trayIconService?.Dispose();
+ _mutex?.ReleaseMutex();
+ _mutex?.Dispose();
+ }
}
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
diff --git a/src/CodingWithCalvin.VSToolbox/Services/SettingsService.cs b/src/CodingWithCalvin.VSToolbox/Services/SettingsService.cs
new file mode 100644
index 0000000..0e473f7
--- /dev/null
+++ b/src/CodingWithCalvin.VSToolbox/Services/SettingsService.cs
@@ -0,0 +1,169 @@
+using System.Text.Json;
+using Microsoft.Win32;
+
+namespace CodingWithCalvin.VSToolbox.Services;
+
+public class SettingsService
+{
+ private const string StartupRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
+ private const string AppName = "VSToolbox";
+ private const string SettingsFileName = "settings.json";
+
+ private static readonly string SettingsFilePath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "CodingWithCalvin.VSToolbox",
+ SettingsFileName);
+
+ private static SettingsData? _settings;
+ private static readonly object _lock = new();
+
+ private static SettingsData Settings
+ {
+ get
+ {
+ if (_settings is null)
+ {
+ lock (_lock)
+ {
+ _settings ??= LoadSettings();
+ }
+ }
+ return _settings;
+ }
+ }
+
+ public bool LaunchOnStartup
+ {
+ get => Settings.LaunchOnStartup;
+ set
+ {
+ Settings.LaunchOnStartup = value;
+ SaveSettings();
+ UpdateStartupRegistration(value);
+ }
+ }
+
+ public bool StartMinimized
+ {
+ get => Settings.StartMinimized;
+ set
+ {
+ Settings.StartMinimized = value;
+ SaveSettings();
+ }
+ }
+
+ public bool MinimizeToTray
+ {
+ get => Settings.MinimizeToTray;
+ set
+ {
+ Settings.MinimizeToTray = value;
+ SaveSettings();
+ }
+ }
+
+ public bool CloseToTray
+ {
+ get => Settings.CloseToTray;
+ set
+ {
+ Settings.CloseToTray = value;
+ SaveSettings();
+ }
+ }
+
+ private static SettingsData LoadSettings()
+ {
+ try
+ {
+ if (File.Exists(SettingsFilePath))
+ {
+ var json = File.ReadAllText(SettingsFilePath);
+ return JsonSerializer.Deserialize(json) ?? new SettingsData();
+ }
+ }
+ catch
+ {
+ // If we can't load settings, return defaults
+ }
+ return new SettingsData();
+ }
+
+ private static void SaveSettings()
+ {
+ try
+ {
+ var directory = Path.GetDirectoryName(SettingsFilePath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ var json = JsonSerializer.Serialize(_settings, new JsonSerializerOptions { WriteIndented = true });
+ File.WriteAllText(SettingsFilePath, json);
+ }
+ catch
+ {
+ // Silently fail if we can't save settings
+ }
+ }
+
+ private static void UpdateStartupRegistration(bool enable)
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(StartupRegistryKey, writable: true);
+ if (key is null) return;
+
+ if (enable)
+ {
+ var exePath = Environment.ProcessPath;
+ if (!string.IsNullOrEmpty(exePath))
+ {
+ key.SetValue(AppName, $"\"{exePath}\"");
+ }
+ }
+ else
+ {
+ key.DeleteValue(AppName, throwOnMissingValue: false);
+ }
+ }
+ catch
+ {
+ // Silently fail if we can't access the registry
+ }
+ }
+
+ ///
+ /// Checks if the app is currently registered for startup and syncs the setting.
+ /// Call this on app startup to ensure settings match actual state.
+ ///
+ public void SyncStartupSetting()
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(StartupRegistryKey, writable: false);
+ var isRegistered = key?.GetValue(AppName) is not null;
+
+ // Update setting to match registry state
+ if (Settings.LaunchOnStartup != isRegistered)
+ {
+ Settings.LaunchOnStartup = isRegistered;
+ SaveSettings();
+ }
+ }
+ catch
+ {
+ // Silently fail
+ }
+ }
+
+ private class SettingsData
+ {
+ public bool LaunchOnStartup { get; set; }
+ public bool StartMinimized { get; set; }
+ public bool MinimizeToTray { get; set; } = true;
+ public bool CloseToTray { get; set; } = true;
+ }
+}
diff --git a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
index 73ce610..82e8917 100644
--- a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
+++ b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
@@ -89,13 +89,11 @@
-
+ Style="{StaticResource PillTabStyle}">
@@ -241,16 +239,100 @@
-
-
+
+
+ Margin="0,0,0,4"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Text="Behavior"
+ FontSize="14"
+ FontWeight="SemiBold"
+ Margin="0,8,0,4"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
index 528ce20..44b632a 100644
--- a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
+++ b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
@@ -16,14 +16,34 @@ public sealed partial class MainPage : Page
private static readonly SolidColorBrush TransparentBrush = new(Colors.Transparent);
private static readonly SolidColorBrush HoverBackgroundBrush = new(Color.FromArgb(20, 138, 43, 226));
+ private readonly SettingsService _settingsService = new();
+ private bool _isInitializingSettings;
+
public MainPage()
{
InitializeComponent();
ViewModel = new MainViewModel();
+ InitializeSettings();
}
public MainViewModel ViewModel { get; }
+ private void InitializeSettings()
+ {
+ _isInitializingSettings = true;
+
+ // Sync startup setting with registry state
+ _settingsService.SyncStartupSetting();
+
+ // Load current settings into toggles
+ LaunchOnStartupToggle.IsOn = _settingsService.LaunchOnStartup;
+ StartMinimizedToggle.IsOn = _settingsService.StartMinimized;
+ MinimizeToTrayToggle.IsOn = _settingsService.MinimizeToTray;
+ CloseToTrayToggle.IsOn = _settingsService.CloseToTray;
+
+ _isInitializingSettings = false;
+ }
+
private async void OnPageLoaded(object sender, RoutedEventArgs e)
{
// Set the title bar drag region
@@ -188,13 +208,18 @@ private void OnTabChanged(object sender, RoutedEventArgs e)
private void OnMinimizeClick(object sender, RoutedEventArgs e)
{
- var window = App.Current as App;
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(((App)Application.Current).MainWindow);
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
var appWindow = AppWindow.GetFromWindowId(windowId);
- if (appWindow.Presenter is OverlappedPresenter presenter)
+ if (_settingsService.MinimizeToTray)
{
+ // Hide to system tray
+ appWindow.Hide();
+ }
+ else if (appWindow.Presenter is OverlappedPresenter presenter)
+ {
+ // Normal minimize
presenter.Minimize();
}
}
@@ -205,7 +230,39 @@ private void OnCloseClick(object sender, RoutedEventArgs e)
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
var appWindow = AppWindow.GetFromWindowId(windowId);
- // This will trigger the Closing event which hides instead of closes
- appWindow.Hide();
+ if (_settingsService.CloseToTray)
+ {
+ // Hide to system tray
+ appWindow.Hide();
+ }
+ else
+ {
+ // Actually close the app
+ Application.Current.Exit();
+ }
+ }
+
+ private void OnLaunchOnStartupToggled(object sender, RoutedEventArgs e)
+ {
+ if (_isInitializingSettings) return;
+ _settingsService.LaunchOnStartup = LaunchOnStartupToggle.IsOn;
+ }
+
+ private void OnStartMinimizedToggled(object sender, RoutedEventArgs e)
+ {
+ if (_isInitializingSettings) return;
+ _settingsService.StartMinimized = StartMinimizedToggle.IsOn;
+ }
+
+ private void OnMinimizeToTrayToggled(object sender, RoutedEventArgs e)
+ {
+ if (_isInitializingSettings) return;
+ _settingsService.MinimizeToTray = MinimizeToTrayToggle.IsOn;
+ }
+
+ private void OnCloseToTrayToggled(object sender, RoutedEventArgs e)
+ {
+ if (_isInitializingSettings) return;
+ _settingsService.CloseToTray = CloseToTrayToggle.IsOn;
}
}