From eac9888eb15efcb2ff913e21c6aab6c6ac425881 Mon Sep 17 00:00:00 2001 From: Blake-goofy Date: Mon, 16 Mar 2026 18:54:31 -0500 Subject: [PATCH 1/2] Add theme support for settings, about, and data transfer pages --- AxialSqlTools/AxialSqlTools.csproj | 1 + .../DataTransferWindowControl.xaml | 142 ++++++++++- .../DataTransferWindowControl.xaml.cs | 105 ++++++++ AxialSqlTools/Modules/VsThemeBrushResolver.cs | 66 +++++ .../WindowSettings/SettingsWindowControl.xaml | 232 +++++++++++++++++- .../SettingsWindowControl.xaml.cs | 133 +++++++++- AxialSqlTools/source.extension.vsixmanifest | 2 +- 7 files changed, 670 insertions(+), 11 deletions(-) create mode 100644 AxialSqlTools/Modules/VsThemeBrushResolver.cs diff --git a/AxialSqlTools/AxialSqlTools.csproj b/AxialSqlTools/AxialSqlTools.csproj index b567e7b..3c1ca80 100644 --- a/AxialSqlTools/AxialSqlTools.csproj +++ b/AxialSqlTools/AxialSqlTools.csproj @@ -143,6 +143,7 @@ + diff --git a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml index 0ba5480..336db99 100644 --- a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml +++ b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml @@ -5,11 +5,147 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" mc:Ignorable="d" FontSize="14" - Name="MyToolWindow"> - + Name="MyToolWindow" + Background="{DynamicResource AxialDataTransferBackgroundBrush}" + Foreground="{DynamicResource AxialDataTransferForegroundBrush}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + Data Transfer diff --git a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml.cs b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml.cs index 11b8f65..09b8193 100644 --- a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml.cs +++ b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml.cs @@ -1,5 +1,6 @@ namespace AxialSqlTools { + using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using MySqlConnector; using Npgsql; @@ -17,6 +18,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Documents; + using System.Windows.Media; /// /// Interaction logic for DataTransferWindowControl. @@ -26,6 +28,7 @@ public partial class DataTransferWindowControl : UserControl private CancellationTokenSource _cancellationTokenSource; private Stopwatch stopwatch; + private bool _isThemeSubscribed; private string sourceConnectionString = ""; private string targetConnectionString = ""; @@ -69,6 +72,10 @@ public static int GetRowsCopied(SqlBulkCopy bulkCopy) public DataTransferWindowControl() { this.InitializeComponent(); + ApplyThemeBrushResources(); + this.Loaded += DataTransferWindowControl_Loaded; + this.Unloaded += DataTransferWindowControl_Unloaded; + this.IsVisibleChanged += DataTransferWindowControl_IsVisibleChanged; Button_CopyData.IsEnabled = false; ButtonToPsql_CopyData.IsEnabled = false; @@ -109,6 +116,104 @@ public DataTransferWindowControl() } + private void DataTransferWindowControl_Loaded(object sender, RoutedEventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + ApplyThemeBrushResources(); + SubscribeToThemeChanges(); + } + + private void DataTransferWindowControl_Unloaded(object sender, RoutedEventArgs e) + { + UnsubscribeFromThemeChanges(); + } + + private void DataTransferWindowControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (IsVisible) + { + ApplyThemeBrushResources(); + } + } + + private void SubscribeToThemeChanges() + { + if (_isThemeSubscribed) + { + return; + } + + VSColorTheme.ThemeChanged += OnVsThemeChanged; + _isThemeSubscribed = true; + } + + private void UnsubscribeFromThemeChanges() + { + if (!_isThemeSubscribed) + { + return; + } + + VSColorTheme.ThemeChanged -= OnVsThemeChanged; + _isThemeSubscribed = false; + } + + private void OnVsThemeChanged(ThemeChangedEventArgs e) + { + if (!Dispatcher.CheckAccess()) + { + Dispatcher.BeginInvoke(new Action(ApplyThemeBrushResources)); + return; + } + + ApplyThemeBrushResources(); + } + + private void ApplyThemeBrushResources() + { + Brush bg = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowBackgroundBrushKey) + ?? SystemColors.WindowBrush; + Brush fg = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowTextBrushKey) + ?? SystemColors.WindowTextBrush; + Brush border = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowBorderBrushKey) + ?? SystemColors.ActiveBorderBrush; + + Color bgColor = VsThemeBrushResolver.GetBrushColor(bg, Colors.White); + bool isLightTheme = VsThemeBrushResolver.GetRelativeLuminance(bgColor) > 0.6; + + Color headerColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.04) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.04); + Color tabHeaderColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.05) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.06); + Color tabHoverColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.10) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.12); + Color tabSelectedColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.16) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.18); + Color buttonHoverColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.08) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.08); + Color buttonPressedColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.14) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.14); + + Resources["AxialDataTransferBackgroundBrush"] = bg; + Resources["AxialDataTransferForegroundBrush"] = fg; + Resources["AxialDataTransferBorderBrush"] = border; + Resources["AxialDataTransferHeaderBackgroundBrush"] = new SolidColorBrush(headerColor); + Resources["AxialDataTransferTabHeaderBackgroundBrush"] = new SolidColorBrush(tabHeaderColor); + Resources["AxialDataTransferTabHeaderHoverBrush"] = new SolidColorBrush(tabHoverColor); + Resources["AxialDataTransferTabHeaderSelectedBrush"] = new SolidColorBrush(tabSelectedColor); + Resources["AxialDataTransferButtonHoverBrush"] = new SolidColorBrush(buttonHoverColor); + Resources["AxialDataTransferButtonPressedBrush"] = new SolidColorBrush(buttonPressedColor); + } + private void Button_EditSavedConnections_Click(object sender, RoutedEventArgs e) { var window = new SavedConnectionManagerWindow diff --git a/AxialSqlTools/Modules/VsThemeBrushResolver.cs b/AxialSqlTools/Modules/VsThemeBrushResolver.cs new file mode 100644 index 0000000..7465b49 --- /dev/null +++ b/AxialSqlTools/Modules/VsThemeBrushResolver.cs @@ -0,0 +1,66 @@ +namespace AxialSqlTools +{ + using System; + using System.Reflection; + using System.Windows; + using System.Windows.Media; + using Microsoft.VisualStudio.PlatformUI; + + internal static class VsThemeBrushResolver + { + public static Brush ResolveBrush(FrameworkElement scope, object resourceKey) + { + if (resourceKey == null) + { + return null; + } + + return scope?.TryFindResource(resourceKey) as Brush + ?? Application.Current?.TryFindResource(resourceKey) as Brush; + } + + public static Brush ResolveEnvironmentBrushByName(FrameworkElement scope, string keyName) + { + if (string.IsNullOrWhiteSpace(keyName)) + { + return null; + } + + PropertyInfo property = typeof(EnvironmentColors).GetProperty(keyName, BindingFlags.Public | BindingFlags.Static); + object key = property?.GetValue(null); + return ResolveBrush(scope, key); + } + + public static Color GetBrushColor(Brush brush, Color fallback) + { + if (brush is SolidColorBrush solidBrush) + { + return solidBrush.Color; + } + + return fallback; + } + + public static double GetRelativeLuminance(Color color) + { + double r = color.R / 255.0; + double g = color.G / 255.0; + double b = color.B / 255.0; + + double rLinear = r <= 0.03928 ? r / 12.92 : Math.Pow((r + 0.055) / 1.055, 2.4); + double gLinear = g <= 0.03928 ? g / 12.92 : Math.Pow((g + 0.055) / 1.055, 2.4); + double bLinear = b <= 0.03928 ? b / 12.92 : Math.Pow((b + 0.055) / 1.055, 2.4); + + return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear; + } + + public static Color BlendColors(Color baseColor, Color blendColor, double blendAmount) + { + blendAmount = Math.Max(0.0, Math.Min(1.0, blendAmount)); + byte r = (byte)Math.Round((baseColor.R * (1.0 - blendAmount)) + (blendColor.R * blendAmount)); + byte g = (byte)Math.Round((baseColor.G * (1.0 - blendAmount)) + (blendColor.G * blendAmount)); + byte b = (byte)Math.Round((baseColor.B * (1.0 - blendAmount)) + (blendColor.B * blendAmount)); + return Color.FromRgb(r, g, b); + } + } +} \ No newline at end of file diff --git a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml index 6d60236..028fd7d 100644 --- a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml +++ b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml @@ -4,12 +4,233 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" - Name="MyToolWindow"> + Name="MyToolWindow" + Background="{DynamicResource AxialSettingsBackgroundBrush}" + Foreground="{DynamicResource AxialSettingsForegroundBrush}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + Background="{DynamicResource AxialSettingsHeaderBackgroundBrush}" + Padding="12" BorderBrush="{DynamicResource AxialSettingsBorderBrush}" BorderThickness="0,0,0,1"> diff --git a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs index 89a965e..acfb7b5 100644 --- a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs +++ b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs @@ -1,5 +1,7 @@ namespace AxialSqlTools { + using Microsoft.VisualStudio.PlatformUI; + using Microsoft.VisualStudio.Shell; using Microsoft.Data.SqlClient; using Newtonsoft.Json.Linq; using System; @@ -24,6 +26,7 @@ public partial class SettingsWindowControl : UserControl { private string _queryHistoryConnectionString; + private bool _isThemeSubscribed; private string tsqlFormatExample = @"while (1=0) begin @@ -49,9 +52,12 @@ public SettingsWindowControl() { this.InitializeComponent(); + ApplyThemeBrushResources(); LoadSavedSettings(); this.Loaded += UserControl_Loaded; + this.Unloaded += UserControl_Unloaded; + this.IsVisibleChanged += SettingsWindowControl_IsVisibleChanged; SourceQueryPreview.Text = tsqlFormatExample; @@ -61,9 +67,132 @@ public SettingsWindowControl() private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + + ApplyThemeBrushResources(); + SubscribeToThemeChanges(); LoadSavedSettings(); } + private void UserControl_Unloaded(object sender, RoutedEventArgs e) + { + UnsubscribeFromThemeChanges(); + } + + private void SettingsWindowControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (IsVisible) + { + ApplyThemeBrushResources(); + } + } + + private void SubscribeToThemeChanges() + { + if (_isThemeSubscribed) + { + return; + } + + VSColorTheme.ThemeChanged += OnVsThemeChanged; + _isThemeSubscribed = true; + } + + private void UnsubscribeFromThemeChanges() + { + if (!_isThemeSubscribed) + { + return; + } + + VSColorTheme.ThemeChanged -= OnVsThemeChanged; + _isThemeSubscribed = false; + } + + private void OnVsThemeChanged(ThemeChangedEventArgs e) + { + if (!Dispatcher.CheckAccess()) + { + Dispatcher.BeginInvoke(new Action(ApplyThemeBrushResources)); + return; + } + + ApplyThemeBrushResources(); + } + + private void ApplyThemeBrushResources() + { + Brush bg = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowBackgroundBrushKey) + ?? SystemColors.WindowBrush; + Brush fg = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowTextBrushKey) + ?? SystemColors.WindowTextBrush; + Brush border = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ToolWindowBorderBrushKey) + ?? SystemColors.ActiveBorderBrush; + Brush link = VsThemeBrushResolver.ResolveBrush(this, EnvironmentColors.ControlLinkTextBrushKey) + ?? SystemColors.HotTrackBrush; + Brush success = VsThemeBrushResolver.ResolveEnvironmentBrushByName(this, "SystemGreenTextBrushKey") + ?? new SolidColorBrush(Color.FromRgb(0x10, 0x7C, 0x10)); + Brush error = VsThemeBrushResolver.ResolveEnvironmentBrushByName(this, "SystemRedTextBrushKey") + ?? new SolidColorBrush(Color.FromRgb(0xA1, 0x26, 0x0D)); + + Color bgColor = VsThemeBrushResolver.GetBrushColor(bg, Colors.White); + bool isLightTheme = VsThemeBrushResolver.GetRelativeLuminance(bgColor) > 0.6; + + Color headerColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.04) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.04); + Color tabHeaderColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.05) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.06); + Color tabHoverColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.10) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.12); + Color tabSelectedColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.16) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.18); + Color buttonHoverColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.08) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.08); + Color buttonPressedColor = isLightTheme + ? VsThemeBrushResolver.BlendColors(bgColor, Colors.Black, 0.14) + : VsThemeBrushResolver.BlendColors(bgColor, Colors.White, 0.14); + + Resources["AxialSettingsBackgroundBrush"] = bg; + Resources["AxialSettingsForegroundBrush"] = fg; + Resources["AxialSettingsBorderBrush"] = border; + Resources["AxialSettingsHeaderBackgroundBrush"] = new SolidColorBrush(headerColor); + Resources["AxialSettingsLinkBrush"] = link; + Resources["AxialSettingsStatusErrorBrush"] = error; + Resources["AxialSettingsStatusSuccessBrush"] = success; + Resources["AxialSettingsTabHeaderBackgroundBrush"] = new SolidColorBrush(tabHeaderColor); + Resources["AxialSettingsTabHeaderHoverBrush"] = new SolidColorBrush(tabHoverColor); + Resources["AxialSettingsTabHeaderSelectedBrush"] = new SolidColorBrush(tabSelectedColor); + Resources["AxialSettingsButtonHoverBrush"] = new SolidColorBrush(buttonHoverColor); + Resources["AxialSettingsButtonPressedBrush"] = new SolidColorBrush(buttonPressedColor); + + ApplyGoogleSheetsAuthorizationBrush(); + } + + private Brush GetThemedStatusBrush(bool isSuccess) + { + string key = isSuccess ? "AxialSettingsStatusSuccessBrush" : "AxialSettingsStatusErrorBrush"; + return Resources[key] as Brush + ?? (isSuccess ? new SolidColorBrush(Color.FromRgb(0x10, 0x7C, 0x10)) : new SolidColorBrush(Color.FromRgb(0xA1, 0x26, 0x0D))); + } + + private void ApplyGoogleSheetsAuthorizationBrush() + { + if (GoogleSheetsRefreshTokenLabel == null) + { + return; + } + + bool isAuthorized = string.Equals(GoogleSheetsRefreshTokenLabel.Text, "Authorized", StringComparison.OrdinalIgnoreCase); + GoogleSheetsRefreshTokenLabel.Foreground = GetThemedStatusBrush(isAuthorized); + } + private void LoadSavedSettings() { try @@ -413,12 +542,12 @@ private void UpdateGoogleSheetsStatus(string refreshToken) if (string.IsNullOrWhiteSpace(refreshToken)) { GoogleSheetsRefreshTokenLabel.Text = "Not authorized"; - GoogleSheetsRefreshTokenLabel.Foreground = new SolidColorBrush(Colors.DarkRed); + GoogleSheetsRefreshTokenLabel.Foreground = GetThemedStatusBrush(false); } else { GoogleSheetsRefreshTokenLabel.Text = "Authorized"; - GoogleSheetsRefreshTokenLabel.Foreground = new SolidColorBrush(Colors.DarkGreen); + GoogleSheetsRefreshTokenLabel.Foreground = GetThemedStatusBrush(true); } } diff --git a/AxialSqlTools/source.extension.vsixmanifest b/AxialSqlTools/source.extension.vsixmanifest index 00dbdb2..7a0add2 100644 --- a/AxialSqlTools/source.extension.vsixmanifest +++ b/AxialSqlTools/source.extension.vsixmanifest @@ -10,7 +10,7 @@ Resources\vsix-logo.png Resources\vsix-logo.png - + amd64 From bf2a5ee90cdbb5e09d491ce823cd111f260516ea Mon Sep 17 00:00:00 2001 From: Blake-goofy Date: Wed, 18 Mar 2026 21:52:07 -0500 Subject: [PATCH 2/2] Add theme for GitHub sync --- AxialSqlTools/AxialSqlTools.csproj | 5 + .../DataTransferWindowControl.xaml | 195 ++----- .../DataTransferWindowControl.xaml.cs | 103 +--- .../Modules/ToolWindowThemeSupport.cs | 197 +++++++ .../QueryHistory/QueryHistoryWindowCommand.cs | 25 +- .../QueryHistoryWindowControl.xaml | 53 +- .../QueryHistoryWindowControl.xaml.cs | 25 + .../DatabaseScripterToolWindowControl.xaml | 65 ++- .../DatabaseScripterToolWindowControl.xaml.cs | 8 + .../Themes/SharedToolWindowTheme.xaml | 504 ++++++++++++++++++ .../WindowSettings/SettingsWindowControl.xaml | 297 ++--------- .../SettingsWindowControl.xaml.cs | 110 +--- 12 files changed, 943 insertions(+), 644 deletions(-) create mode 100644 AxialSqlTools/Modules/ToolWindowThemeSupport.cs create mode 100644 AxialSqlTools/Themes/SharedToolWindowTheme.xaml diff --git a/AxialSqlTools/AxialSqlTools.csproj b/AxialSqlTools/AxialSqlTools.csproj index 3c1ca80..fa66a93 100644 --- a/AxialSqlTools/AxialSqlTools.csproj +++ b/AxialSqlTools/AxialSqlTools.csproj @@ -143,6 +143,7 @@ + @@ -405,6 +406,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + diff --git a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml index 336db99..b91b57f 100644 --- a/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml +++ b/AxialSqlTools/DataTransfer/DataTransferWindowControl.xaml @@ -4,155 +4,37 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" - mc:Ignorable="d" FontSize="14" + mc:Ignorable="d" Name="MyToolWindow" - Background="{DynamicResource AxialDataTransferBackgroundBrush}" - Foreground="{DynamicResource AxialDataTransferForegroundBrush}"> + Background="{DynamicResource AxialThemeBackgroundBrush}" + Foreground="{DynamicResource AxialThemeForegroundBrush}"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + - - Data Transfer + + + Data Transfer + + + + + + + - -