Skip to content

Commit 2612a47

Browse files
authored
feat(ui): add custom logo and improve tool window toggling (#1)
- Add custom rocket logo with purple/magenta flames (SVG and PNG) - Display logo at top of sidebar with separator - Implement tool window toggle functionality (show/hide on click) - Add DebugStateService to track debugger mode and update icon - Remove Options page and config.json persistence - Remove VSCommandTable.vsct (unused) - Optimize initialization with exponential backoff - Update default items: Terminal at bottom, improved icons - Update README with current features and default items
1 parent 614c815 commit 2612a47

15 files changed

Lines changed: 403 additions & 273 deletions

README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ If you've used VS Code or JetBrains IDEs, you're probably familiar with the Acti
4242

4343
- 🎯 **Quick Access** - Launch frequently used tools with a single click
4444
- 🔧 **VS Commands** - Execute any Visual Studio command directly
45-
- 🪟 **Tool Windows** - Toggle tool windows like Solution Explorer, Terminal, and Git Changes
46-
- 🚀 **External Programs** - Launch external applications from within VS
47-
- ⚙️ **Customizable** - Add, remove, and reorder items to fit your workflow
45+
- 🪟 **Tool Windows** - Toggle tool windows like Solution Explorer, Terminal, and Git Changes (click again to hide)
46+
- 🐞 **Smart Debug Button** - Automatically switches between Start and Stop based on debugger state
4847
- 🎨 **Native Look** - Seamlessly integrates with Visual Studio's theme
4948

5049
## 💡 Pro Tip
@@ -68,10 +67,24 @@ Download the latest `.vsix` file from the [Releases](https://github.com/CodingWi
6867

6968
1. After installation, LaunchyBar automatically appears docked to the left side of Visual Studio
7069
2. Click any icon to execute its associated action
71-
3. To customize, go to **Tools** > **Options** > **LaunchyBar**
72-
4. Add your own commands, tool windows, or external programs
70+
3. Tool windows (Solution Explorer, Terminal, Git Changes) toggle on each click - click once to show, click again to hide
71+
4. The Debug button automatically shows "Start Debugging" or "Stop Debugging" based on the current debugger state
7372
5. Enjoy quick access to your favorite tools! 🎉
7473

74+
## 📋 Default Items
75+
76+
LaunchyBar comes preconfigured with these items:
77+
78+
**Top Section:**
79+
- 📂 Solution Explorer
80+
- 🔍 Find in Files
81+
- 🔀 Git Changes
82+
- ▶️ Debug (Start/Stop)
83+
84+
**Bottom Section:**
85+
- 💻 Terminal
86+
- ⚙️ Settings
87+
7588
## 🤝 Contributing
7689

7790
Contributions are welcome! Please feel free to submit a Pull Request.

resources/logo-32.png

2.22 KB
Loading

resources/logo.png

89.2 KB
Loading

resources/logo.svg

Lines changed: 89 additions & 0 deletions
Loading

src/CodingWithCalvin.LaunchyBar/CodingWithCalvin.LaunchyBar.csproj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@
2121
</ItemGroup>
2222

2323
<ItemGroup>
24+
<Resource Include="..\..\resources\logo-32.png" Link="Resources\logo-32.png" />
2425
<Content Include="..\..\resources\logo.png" Link="Resources\logo.png">
2526
<IncludeInVSIX>true</IncludeInVSIX>
2627
</Content>
27-
<Content Include="..\..\resources\preview.png" Link="Resources\preview.png">
28-
<IncludeInVSIX>true</IncludeInVSIX>
29-
</Content>
30-
<Content Include="..\..\resources\CommandIcon.png" Link="Resources\CommandIcon.png">
31-
<IncludeInVSIX>true</IncludeInVSIX>
32-
</Content>
3328
<Content Include="..\..\LICENSE" Link="Resources\LICENSE">
3429
<IncludeInVSIX>true</IncludeInVSIX>
3530
</Content>

src/CodingWithCalvin.LaunchyBar/LaunchyBarPackage.cs

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using System;
2-
using System.Diagnostics;
32
using System.Runtime.InteropServices;
43
using System.Threading;
5-
using CodingWithCalvin.LaunchyBar.Options;
64
using CodingWithCalvin.LaunchyBar.Services;
75
using CodingWithCalvin.Otel4Vsix;
86
using Microsoft.VisualStudio;
@@ -12,16 +10,16 @@
1210
namespace CodingWithCalvin.LaunchyBar;
1311

1412
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
15-
[Guid(VSCommandTableVsct.guidLaunchyBarPackageString)]
13+
[Guid("F1D884BA-D328-4A15-89F4-ECACCCD022D1")]
1614
[ProvideAutoLoad(VSConstants.UICONTEXT.ShellInitialized_string, PackageAutoLoadFlags.BackgroundLoad)]
17-
[ProvideOptionPage(typeof(OptionsProvider.GeneralOptionsPage), "LaunchyBar", "General", 0, 0, true)]
1815
public sealed class LaunchyBarPackage : AsyncPackage
1916
{
2017
public static LaunchyBarPackage? Instance { get; private set; }
2118

2219
private IConfigurationService? _configurationService;
2320
private ILaunchService? _launchService;
2421
private IShellInjectionService? _shellInjectionService;
22+
private DebugStateService? _debugStateService;
2523

2624
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
2725
{
@@ -47,53 +45,40 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
4745

4846
try
4947
{
50-
Debug.WriteLine("LaunchyBar: Initializing services...");
51-
5248
// Initialize services
5349
_configurationService = new ConfigurationService();
54-
Debug.WriteLine("LaunchyBar: ConfigurationService created");
55-
5650
_launchService = new LaunchService(this);
57-
Debug.WriteLine("LaunchyBar: LaunchService created");
58-
59-
// Inject the bar into VS shell with retries
60-
Debug.WriteLine("LaunchyBar: Creating ShellInjectionService...");
6151
_shellInjectionService = new ShellInjectionService(_configurationService, _launchService);
62-
Debug.WriteLine("LaunchyBar: ShellInjectionService created");
6352

64-
// Retry injection until successful or max attempts reached
65-
const int maxAttempts = 10;
53+
// Try injection immediately, then retry with increasing delays
54+
const int maxAttempts = 8;
55+
int delayMs = 100;
56+
6657
for (int attempt = 1; attempt <= maxAttempts; attempt++)
6758
{
68-
Debug.WriteLine($"LaunchyBar: Injection attempt {attempt}/{maxAttempts}...");
69-
await Task.Delay(1000, cancellationToken);
70-
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
71-
72-
var result = _shellInjectionService.Inject();
73-
if (result)
59+
if (_shellInjectionService.Inject())
7460
{
75-
Debug.WriteLine("LaunchyBar: Injection successful!");
61+
_debugStateService = new DebugStateService(this, _configurationService);
7662
break;
7763
}
7864

79-
Debug.WriteLine($"LaunchyBar: Attempt {attempt} failed, will retry...");
65+
// Wait before next attempt (100, 200, 400, 800, 1000, 1000, 1000, 1000)
66+
await Task.Delay(delayMs, cancellationToken);
67+
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
68+
delayMs = Math.Min(delayMs * 2, 1000);
8069
}
8170
}
82-
catch (Exception ex)
71+
catch
8372
{
84-
Debug.WriteLine($"LaunchyBar: EXCEPTION - {ex.GetType().Name}: {ex.Message}");
85-
Debug.WriteLine($"LaunchyBar: Stack trace: {ex.StackTrace}");
86-
if (ex.InnerException != null)
87-
{
88-
Debug.WriteLine($"LaunchyBar: Inner exception - {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
89-
}
73+
// Initialization failed - extension will not be functional
9074
}
9175
}
9276

9377
protected override void Dispose(bool disposing)
9478
{
9579
if (disposing)
9680
{
81+
_debugStateService?.Dispose();
9782
_shellInjectionService?.Dispose();
9883
Instance = null;
9984
VsixTelemetry.Shutdown();

src/CodingWithCalvin.LaunchyBar/Models/LaunchItem.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.ComponentModel;
3+
using System.Runtime.CompilerServices;
24
using System.Text.Json.Serialization;
35
using Microsoft.VisualStudio.Imaging;
46
using Microsoft.VisualStudio.Imaging.Interop;
@@ -8,8 +10,14 @@ namespace CodingWithCalvin.LaunchyBar.Models;
810
/// <summary>
911
/// Represents a single item in the LaunchyBar.
1012
/// </summary>
11-
public sealed class LaunchItem
13+
public sealed class LaunchItem : INotifyPropertyChanged
1214
{
15+
private string _iconPath = string.Empty;
16+
private string _name = string.Empty;
17+
18+
/// <inheritdoc/>
19+
public event PropertyChangedEventHandler? PropertyChanged;
20+
1321
/// <summary>
1422
/// Unique identifier for the launch item.
1523
/// </summary>
@@ -18,12 +26,35 @@ public sealed class LaunchItem
1826
/// <summary>
1927
/// Display name shown in tooltip.
2028
/// </summary>
21-
public string Name { get; set; } = string.Empty;
29+
public string Name
30+
{
31+
get => _name;
32+
set
33+
{
34+
if (_name != value)
35+
{
36+
_name = value;
37+
OnPropertyChanged();
38+
}
39+
}
40+
}
2241

2342
/// <summary>
2443
/// Path to icon file, embedded resource, or VS ImageMoniker name.
2544
/// </summary>
26-
public string IconPath { get; set; } = string.Empty;
45+
public string IconPath
46+
{
47+
get => _iconPath;
48+
set
49+
{
50+
if (_iconPath != value)
51+
{
52+
_iconPath = value;
53+
OnPropertyChanged();
54+
OnPropertyChanged(nameof(IconMoniker));
55+
}
56+
}
57+
}
2758

2859
/// <summary>
2960
/// The type of action this item performs.
@@ -85,4 +116,9 @@ private ImageMoniker GetIconMoniker()
85116

86117
return KnownMonikers.QuestionMark;
87118
}
119+
120+
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
121+
{
122+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
123+
}
88124
}

src/CodingWithCalvin.LaunchyBar/Models/LaunchyBarConfiguration.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,21 @@ public static LaunchyBarConfiguration CreateDefault()
4545
{
4646
Id = "search",
4747
Name = "Find in Files",
48-
IconPath = "KnownMonikers.SearchFiles",
48+
IconPath = "KnownMonikers.Search",
4949
Type = LaunchItemType.VsCommand,
50-
Target = "Edit.FindinFiles",
50+
Target = "Edit.FindInFiles",
5151
Position = LaunchItemPosition.Top,
5252
Order = 1
5353
},
5454
new()
55-
{
56-
Id = "terminal",
57-
Name = "Terminal",
58-
IconPath = "KnownMonikers.Console",
59-
Type = LaunchItemType.VsCommand,
60-
Target = "View.Terminal",
61-
Position = LaunchItemPosition.Top,
62-
Order = 2
63-
},
64-
new()
6555
{
6656
Id = "git-changes",
6757
Name = "Git Changes",
68-
IconPath = "KnownMonikers.GitLogo",
69-
Type = LaunchItemType.VsCommand,
58+
IconPath = "KnownMonikers.Git",
59+
Type = LaunchItemType.ToolWindow,
7060
Target = "View.GitWindow",
7161
Position = LaunchItemPosition.Top,
72-
Order = 3
62+
Order = 2
7363
},
7464
new()
7565
{
@@ -79,7 +69,17 @@ public static LaunchyBarConfiguration CreateDefault()
7969
Type = LaunchItemType.VsCommand,
8070
Target = "Debug.Start",
8171
Position = LaunchItemPosition.Top,
82-
Order = 4
72+
Order = 3
73+
},
74+
new()
75+
{
76+
Id = "terminal",
77+
Name = "Terminal",
78+
IconPath = "KnownMonikers.Console",
79+
Type = LaunchItemType.ToolWindow,
80+
Target = "View.Terminal",
81+
Position = LaunchItemPosition.Bottom,
82+
Order = 0
8383
},
8484
new()
8585
{
@@ -89,7 +89,7 @@ public static LaunchyBarConfiguration CreateDefault()
8989
Type = LaunchItemType.VsCommand,
9090
Target = "Tools.Options",
9191
Position = LaunchItemPosition.Bottom,
92-
Order = 0
92+
Order = 1
9393
}
9494
}
9595
};

0 commit comments

Comments
 (0)