Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/CodingWithCalvin.VSToolbox/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,64 @@
<Setter Property="Padding" Value="8"/>
<Setter Property="Opacity" Value="0.5"/>
</Style>

<!-- Modern pill-style tab for RadioButton -->
<Style x:Key="PillTabStyle" TargetType="RadioButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="12,8"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="CornerRadius" Value="6"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid x:Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}"/>
<Setter Target="ActiveIndicator.Opacity" Value="1"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked"/>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border
x:Name="RootBorder"
Grid.Row="0"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}">
<ContentPresenter
x:Name="ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<!-- Active indicator bar -->
<Border
x:Name="ActiveIndicator"
Grid.Row="1"
Height="3"
Margin="4,4,4,0"
CornerRadius="2"
Background="#68217A"
Opacity="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
128 changes: 128 additions & 0 deletions src/CodingWithCalvin.VSToolbox/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System.Threading;
using CodingWithCalvin.VSToolbox.Services;
using Microsoft.UI;
using Microsoft.UI.Windowing;
Expand All @@ -7,19 +8,83 @@

namespace CodingWithCalvin.VSToolbox;

// Windows API for setting window corner preference
internal static class NativeMethods
{
[DllImport("dwmapi.dll")]
internal static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

[DllImport("user32.dll")]
internal static extern int GetWindowLong(IntPtr hwnd, int nIndex);

[DllImport("user32.dll")]
internal static extern int SetWindowLong(IntPtr hwnd, int nIndex, int dwNewLong);

[DllImport("user32.dll")]
internal static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

internal const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
internal const int DWMWCP_DONOTROUND = 1;
internal const int DWMWA_CAPTION_COLOR = 35;
internal const int DWMWA_BORDER_COLOR = 34;

internal const int GWL_STYLE = -16;
internal const int WS_CAPTION = 0x00C00000;
internal const int WS_THICKFRAME = 0x00040000;
internal const uint SWP_FRAMECHANGED = 0x0020;
internal const uint SWP_NOMOVE = 0x0002;
internal const uint SWP_NOSIZE = 0x0001;
internal const uint SWP_NOZORDER = 0x0004;

// For finding and showing existing window
internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);

[DllImport("user32.dll")]
internal static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

[DllImport("user32.dll")]
internal static extern bool SetForegroundWindow(IntPtr hWnd);

internal const int SW_RESTORE = 9;
internal const int SW_SHOW = 5;
}

public partial class App : Application
{
private const string MutexName = "CodingWithCalvin.VSToolbox.SingleInstance";
private static Mutex? _mutex;
private Window? _window;
private AppWindow? _appWindow;
private TrayIconService? _trayIconService;

public Window? MainWindow => _window;

public App()
{
InitializeComponent();
}

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// Check for single instance
_mutex = new Mutex(true, MutexName, out var createdNew);
if (!createdNew)
{
// Another instance is already running - try to bring it to front
BringExistingInstanceToFront();
Environment.Exit(0);
return;
}

_window = new Window
{
Title = "Visual Studio Toolbox"
Expand All @@ -37,6 +102,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
_appWindow.SetIcon(iconPath);
}

// Configure custom title bar with square corners
ConfigureCustomTitleBar();

// Set up the main content
var rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Expand All @@ -57,6 +125,41 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
_window.Activate();
}

private void ConfigureCustomTitleBar()
{
if (_appWindow is null || _window is null) return;

// Get the window handle for native API calls
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_window);

// Set square corners using DWM API
var cornerPreference = NativeMethods.DWMWCP_DONOTROUND;
NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_WINDOW_CORNER_PREFERENCE,
ref cornerPreference, sizeof(int));

// Set caption and border color to purple (#68217A = 0x007A2168 in COLORREF BGR format)
var purpleColor = 0x007A2168; // BGR format for #68217A
NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_CAPTION_COLOR,
ref purpleColor, sizeof(int));
NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_BORDER_COLOR,
ref purpleColor, sizeof(int));

// Remove the caption from window style to eliminate the title bar area
var style = NativeMethods.GetWindowLong(hwnd, NativeMethods.GWL_STYLE);
style &= ~NativeMethods.WS_CAPTION; // Remove caption
NativeMethods.SetWindowLong(hwnd, NativeMethods.GWL_STYLE, style);
NativeMethods.SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0,
NativeMethods.SWP_FRAMECHANGED | NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOZORDER);

// Make window borderless (no system title bar at all)
if (_appWindow.Presenter is OverlappedPresenter presenter)
{
presenter.SetBorderAndTitleBar(false, false);
presenter.IsResizable = true;
presenter.IsMaximizable = false;
}
}

private void PositionWindowBottomRight(int width, int height)
{
if (_appWindow is null) return;
Expand Down Expand Up @@ -86,4 +189,29 @@ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new InvalidOperationException($"Failed to load Page {e.SourcePageType.FullName}");
}

private static void BringExistingInstanceToFront()
{
const string windowTitle = "Visual Studio Toolbox";
IntPtr foundWindow = IntPtr.Zero;

NativeMethods.EnumWindows((hWnd, lParam) =>
{
var sb = new System.Text.StringBuilder(256);
NativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
if (sb.ToString() == windowTitle)
{
foundWindow = hWnd;
return false; // Stop enumeration
}
return true; // Continue enumeration
}, IntPtr.Zero);

if (foundWindow != IntPtr.Zero)
{
// Show and bring the window to front
NativeMethods.ShowWindow(foundWindow, NativeMethods.SW_RESTORE);
NativeMethods.SetForegroundWindow(foundWindow);
}
}
}
Loading