9 Commits

Author SHA1 Message Date
Ray Berezowski
e956760a0d Prepare v1.0.2 release 2026-09-04 14:52:59 -04:00
Ray Berezowski
b96347843c Polish settings item editor layout 2026-08-26 22:48:29 -04:00
Ray Berezowski
0baf87d979 Add launcher item notes 2026-08-26 22:43:58 -04:00
Ray Berezowski
57b2c4b0cc Add working folder and minimized launch options 2026-08-26 22:37:09 -04:00
Ray Berezowski
c5fd18e6d3 Fix admin launch and settings visibility 2026-08-26 22:30:00 -04:00
Ray Berezowski
1601da8155 Add run as administrator option 2026-08-26 22:10:55 -04:00
Ray Berezowski
6d9bbbab02 Add launcher item tooltips 2026-08-26 22:04:26 -04:00
Ray Berezowski
5421e3dde3 Restore menu keyboard navigation 2026-08-26 21:56:55 -04:00
Ray Berezowski
004a7e7c89 Polish launcher keyboard navigation 2026-08-26 21:49:23 -04:00
13 changed files with 670 additions and 112 deletions

View File

@@ -8,6 +8,26 @@
MinHeight="660" MinWidth="860" MinHeight="660" MinWidth="860"
Background="{DynamicResource AppWindowBrush}" Background="{DynamicResource AppWindowBrush}"
Foreground="{DynamicResource AppTextBrush}"> Foreground="{DynamicResource AppTextBrush}">
<Window.Resources>
<Style x:Key="SettingsSectionBorder" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource AppPanelBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource AppBorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Margin" Value="0,0,0,12"/>
</Style>
<Style x:Key="SettingsSectionTitle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0,0,0,10"/>
</Style>
<Style x:Key="SettingsFieldLabel" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource AppMutedTextBrush}"/>
<Setter Property="Margin" Value="0,0,8,8"/>
</Style>
</Window.Resources>
<DockPanel Margin="18"> <DockPanel Margin="18">
<Border DockPanel.Dock="Top" <Border DockPanel.Dock="Top"
Margin="0,0,0,16" Margin="0,0,0,16"
@@ -115,8 +135,8 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="16"/> <RowDefinition Height="16"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border Background="{DynamicResource AppPanelBrush}" <Border Background="{DynamicResource AppPanelBrush}"
@@ -134,6 +154,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="Hotkey" VerticalAlignment="Center" Margin="0,0,8,8"/> <TextBlock Text="Hotkey" VerticalAlignment="Center" Margin="0,0,8,8"/>
@@ -158,76 +179,148 @@
<Separator Grid.Row="1" VerticalAlignment="Center"/> <Separator Grid.Row="1" VerticalAlignment="Center"/>
<Border Grid.Row="2" <ScrollViewer Grid.Row="2"
Background="{DynamicResource AppPanelBrush}" VerticalScrollBarVisibility="Auto"
BorderBrush="{DynamicResource AppBorderBrush}" HorizontalScrollBarVisibility="Disabled">
BorderThickness="1" <StackPanel>
CornerRadius="12" <Border Style="{StaticResource SettingsSectionBorder}">
Padding="12"> <Grid>
<Grid> <Grid.ColumnDefinitions>
<Grid.ColumnDefinitions> <ColumnDefinition Width="110"/>
<ColumnDefinition Width="110"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions>
</Grid.ColumnDefinitions> <Grid.RowDefinitions>
<Grid.RowDefinitions> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> </Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Title" VerticalAlignment="Center" Margin="0,0,8,8"/> <TextBlock Text="Item" Grid.ColumnSpan="3" Style="{StaticResource SettingsSectionTitle}"/>
<TextBox x:Name="TitleBox" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8"
TextChanged="PreviewField_Changed"/>
<TextBlock Text="Type" Grid.Row="1" VerticalAlignment="Center" Margin="0,0,8,8"/> <TextBlock Text="Title" Grid.Row="1" Style="{StaticResource SettingsFieldLabel}"/>
<ComboBox x:Name="TypeBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8" <TextBox x:Name="TitleBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8"
SelectionChanged="TypeBox_SelectionChanged"/> TextChanged="PreviewField_Changed"/>
<TextBlock Text="Target" Grid.Row="2" VerticalAlignment="Center" Margin="0,0,8,8"/> <TextBlock Text="Type" Grid.Row="2" Style="{StaticResource SettingsFieldLabel}"/>
<TextBox x:Name="TargetBox" Grid.Row="2" Grid.Column="1" Margin="0,0,8,8" <ComboBox x:Name="TypeBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8"
TextChanged="PreviewField_Changed"/> SelectionChanged="TypeBox_SelectionChanged"/>
<StackPanel Grid.Row="2" Grid.Column="2" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="File" Click="BrowseFile_Click" Margin="0,0,6,0" Padding="8,4"/> <TextBlock Text="Target" Grid.Row="3" Style="{StaticResource SettingsFieldLabel}"/>
<Button Content="Folder" Click="BrowseFolder_Click" Padding="8,4"/> <TextBox x:Name="TargetBox" Grid.Row="3" Grid.Column="1" Margin="0,0,8,8"
TextChanged="PreviewField_Changed"/>
<StackPanel Grid.Row="3" Grid.Column="2" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="File" Click="BrowseFile_Click" Margin="0,0,6,0" Padding="8,4"/>
<Button Content="Folder" Click="BrowseFolder_Click" Padding="8,4"/>
</StackPanel>
<TextBlock Text="Notes" Grid.Row="4" VerticalAlignment="Top" Foreground="{DynamicResource AppMutedTextBrush}" Margin="0,4,8,0"/>
<TextBox x:Name="NotesBox"
Grid.Row="4"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="0"
AcceptsReturn="True"
MinHeight="58"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
TextChanged="PreviewField_Changed"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsSectionBorder}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Launch" Grid.ColumnSpan="3" Style="{StaticResource SettingsSectionTitle}"/>
<TextBlock Text="Arguments" Grid.Row="1" Style="{StaticResource SettingsFieldLabel}"/>
<TextBox x:Name="ArgumentsBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8"/>
<TextBlock Text="Working Folder" Grid.Row="2" Style="{StaticResource SettingsFieldLabel}"/>
<TextBox x:Name="WorkingFolderBox" Grid.Row="2" Grid.Column="1" Margin="0,0,8,8"/>
<Button x:Name="WorkingFolderBrowseButton"
Grid.Row="2" Grid.Column="2"
Content="Folder"
Click="BrowseWorkingFolder_Click"
Padding="8,4"
Margin="0,0,0,8"/>
<TextBlock Text="Options" Grid.Row="3" VerticalAlignment="Top" Foreground="{DynamicResource AppMutedTextBrush}" Margin="0,2,8,0"/>
<StackPanel Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2">
<CheckBox x:Name="RunAsAdminBox"
Content="Run as administrator"
Margin="0,0,0,6"
Checked="LaunchOptionBox_Changed"
Unchecked="LaunchOptionBox_Changed"/>
<CheckBox x:Name="StartMinimizedBox"
Content="Start minimized"
Checked="LaunchOptionBox_Changed"
Unchecked="LaunchOptionBox_Changed"/>
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SettingsSectionBorder}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Appearance" Grid.ColumnSpan="3" Style="{StaticResource SettingsSectionTitle}"/>
<TextBlock Text="Icon Path" Grid.Row="1" Style="{StaticResource SettingsFieldLabel}"/>
<TextBox x:Name="IconPathBox" Grid.Row="1" Grid.Column="1" Margin="0,0,8,8"
TextChanged="PreviewField_Changed"/>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="Icon" Click="BrowseIcon_Click" Padding="8,4" Margin="0,0,6,0"/>
<Button Content="Reset" Click="ResetIcon_Click" Padding="8,4"/>
</StackPanel>
<TextBlock Text="Display" Grid.Row="2" Style="{StaticResource SettingsFieldLabel}"/>
<ComboBox x:Name="ItemDisplayModeBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
Margin="0" SelectionChanged="ItemDisplayModeBox_SelectionChanged"/>
</Grid>
</Border>
<Border Style="{StaticResource SettingsSectionBorder}" Margin="0">
<StackPanel>
<TextBlock Text="Preview" Style="{StaticResource SettingsSectionTitle}"/>
<Border BorderThickness="1"
BorderBrush="{DynamicResource AppBorderBrush}"
Background="{DynamicResource AppSurfaceBrush}"
CornerRadius="12"
Padding="10" MinHeight="76">
<StackPanel>
<ContentControl x:Name="PreviewHost"/>
<TextBlock x:Name="PreviewNote" Margin="0,8,0,0"
Foreground="{DynamicResource AppMutedTextBrush}"
TextWrapping="Wrap"/>
</StackPanel>
</Border>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</ScrollViewer>
<TextBlock Text="Arguments" Grid.Row="3" VerticalAlignment="Center" Margin="0,0,8,8"/>
<TextBox x:Name="ArgumentsBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,0,0,8"/>
<TextBlock Text="Icon Path" Grid.Row="4" VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBox x:Name="IconPathBox" Grid.Row="4" Grid.Column="1" Margin="0,0,8,8"
TextChanged="PreviewField_Changed"/>
<StackPanel Grid.Row="4" Grid.Column="2" Orientation="Horizontal" Margin="0,0,0,8">
<Button Content="Icon" Click="BrowseIcon_Click" Padding="8,4" Margin="0,0,6,0"/>
<Button Content="Reset" Click="ResetIcon_Click" Padding="8,4"/>
</StackPanel>
<TextBlock Text="Selected Item Display" Grid.Row="5" VerticalAlignment="Center" Margin="0,0,8,8"/>
<ComboBox x:Name="ItemDisplayModeBox" Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
Margin="0,0,0,8" SelectionChanged="ItemDisplayModeBox_SelectionChanged"/>
<TextBlock Text="Preview" Grid.Row="6" VerticalAlignment="Top" Margin="0,4,8,0"/>
<Border Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2"
BorderThickness="1"
BorderBrush="{DynamicResource AppBorderBrush}"
Background="{DynamicResource AppSurfaceBrush}"
CornerRadius="12"
Padding="10" MinHeight="76">
<StackPanel>
<ContentControl x:Name="PreviewHost"/>
<TextBlock x:Name="PreviewNote" Margin="0,8,0,0"
Foreground="{DynamicResource AppMutedTextBrush}"
TextWrapping="Wrap"/>
</StackPanel>
</Border>
</Grid>
</Border>
<TextBlock x:Name="StatusText" Grid.Row="3" Margin="4,14,4,0" <TextBlock x:Name="StatusText" Grid.Row="3" Margin="4,14,4,0"
Foreground="{DynamicResource AppMutedTextBrush}" Foreground="{DynamicResource AppMutedTextBrush}"

View File

@@ -613,9 +613,13 @@ public partial class MainWindow : Window
TypeBox.SelectedItem = item.Type; TypeBox.SelectedItem = item.Type;
TargetBox.Text = item.Target; TargetBox.Text = item.Target;
ArgumentsBox.Text = item.Arguments ?? ""; ArgumentsBox.Text = item.Arguments ?? "";
WorkingFolderBox.Text = item.WorkingFolder ?? "";
NotesBox.Text = item.Notes ?? "";
IconPathBox.Text = item.IconPath ?? ""; IconPathBox.Text = item.IconPath ?? "";
loadedAutoIconPath = GetAutomaticIconPath(item); loadedAutoIconPath = GetAutomaticIconPath(item);
ItemDisplayModeBox.SelectedItem = item.DisplayMode; ItemDisplayModeBox.SelectedItem = item.DisplayMode;
RunAsAdminBox.IsChecked = item.RunAsAdmin;
StartMinimizedBox.IsChecked = item.StartMinimized;
UpdateTargetAvailability(); UpdateTargetAvailability();
isLoadingSelection = false; isLoadingSelection = false;
UpdatePreview(); UpdatePreview();
@@ -630,9 +634,13 @@ public partial class MainWindow : Window
TypeBox.SelectedIndex = -1; TypeBox.SelectedIndex = -1;
TargetBox.Text = ""; TargetBox.Text = "";
ArgumentsBox.Text = ""; ArgumentsBox.Text = "";
WorkingFolderBox.Text = "";
NotesBox.Text = "";
IconPathBox.Text = ""; IconPathBox.Text = "";
loadedAutoIconPath = null; loadedAutoIconPath = null;
ItemDisplayModeBox.SelectedItem = controller?.Config.DisplayMode; ItemDisplayModeBox.SelectedItem = controller?.Config.DisplayMode;
RunAsAdminBox.IsChecked = false;
StartMinimizedBox.IsChecked = false;
UpdateTargetAvailability(); UpdateTargetAvailability();
isLoadingSelection = false; isLoadingSelection = false;
UpdatePreview(); UpdatePreview();
@@ -651,12 +659,18 @@ public partial class MainWindow : Window
selectedItem.Arguments = selectedItem.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(ArgumentsBox.Text) selectedItem.Arguments = selectedItem.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(ArgumentsBox.Text)
? null ? null
: ArgumentsBox.Text.Trim(); : ArgumentsBox.Text.Trim();
selectedItem.WorkingFolder = LauncherController.CanUseLaunchOptions(selectedItem.Type) && !string.IsNullOrWhiteSpace(WorkingFolderBox.Text)
? WorkingFolderBox.Text.Trim()
: null;
selectedItem.Notes = string.IsNullOrWhiteSpace(NotesBox.Text) ? null : NotesBox.Text.Trim();
EnsureAutomaticIconPath(); EnsureAutomaticIconPath();
selectedItem.IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(); selectedItem.IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim();
if (ItemDisplayModeBox.SelectedItem is LauncherDisplayMode itemMode) if (ItemDisplayModeBox.SelectedItem is LauncherDisplayMode itemMode)
{ {
selectedItem.DisplayMode = itemMode; selectedItem.DisplayMode = itemMode;
} }
selectedItem.RunAsAdmin = LauncherController.CanRunAsAdmin(selectedItem.Type) && RunAsAdminBox.IsChecked == true;
selectedItem.StartMinimized = LauncherController.CanUseLaunchOptions(selectedItem.Type) && StartMinimizedBox.IsChecked == true;
if (selectedItem.Type != LauncherItemType.Menu) if (selectedItem.Type != LauncherItemType.Menu)
{ {
@@ -963,9 +977,13 @@ public partial class MainWindow : Window
Type = item.Type, Type = item.Type,
Target = item.Target, Target = item.Target,
Arguments = item.Arguments, Arguments = item.Arguments,
WorkingFolder = item.WorkingFolder,
Notes = item.Notes,
IconPath = item.IconPath, IconPath = item.IconPath,
IsFavorite = item.IsFavorite, IsFavorite = item.IsFavorite,
IsDisabled = item.IsDisabled, IsDisabled = item.IsDisabled,
RunAsAdmin = item.RunAsAdmin,
StartMinimized = item.StartMinimized,
DisplayMode = item.DisplayMode, DisplayMode = item.DisplayMode,
Children = item.Children.Select(child => DuplicateItem(child, renameRoot: false)).ToList() Children = item.Children.Select(child => DuplicateItem(child, renameRoot: false)).ToList()
}; };
@@ -1538,6 +1556,11 @@ public partial class MainWindow : Window
issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), problem)); issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), problem));
} }
if (!item.IsDisabled && TryGetWorkingFolderIssue(item, out string? workingFolderProblem) && workingFolderProblem is not null)
{
issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), workingFolderProblem));
}
if (item.Children.Count > 0) if (item.Children.Count > 0)
{ {
ValidateTargets(item.Children, itemPath, issues); ValidateTargets(item.Children, itemPath, issues);
@@ -1606,6 +1629,25 @@ public partial class MainWindow : Window
} }
} }
private static bool TryGetWorkingFolderIssue(LauncherItem item, out string? problem)
{
problem = null;
if (!LauncherController.CanUseLaunchOptions(item.Type) || string.IsNullOrWhiteSpace(item.WorkingFolder))
{
return false;
}
string workingFolder = Environment.ExpandEnvironmentVariables(item.WorkingFolder.Trim());
if (Directory.Exists(workingFolder))
{
return false;
}
problem = "Working folder does not exist.";
return true;
}
private static bool CommandExistsOnPath(string target) private static bool CommandExistsOnPath(string target)
{ {
if (target.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]) >= 0) if (target.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]) >= 0)
@@ -1781,6 +1823,42 @@ public partial class MainWindow : Window
ThemeService.Apply(scheme); ThemeService.Apply(scheme);
} }
private void BrowseWorkingFolder_Click(object sender, RoutedEventArgs e)
{
using FolderBrowserDialog dialog = new()
{
Description = "Choose a working folder"
};
if (!string.IsNullOrWhiteSpace(WorkingFolderBox.Text))
{
string currentFolder = Environment.ExpandEnvironmentVariables(WorkingFolderBox.Text.Trim());
if (Directory.Exists(currentFolder))
{
dialog.SelectedPath = currentFolder;
}
}
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
WorkingFolderBox.Text = dialog.SelectedPath;
}
}
private void LaunchOptionBox_Changed(object sender, RoutedEventArgs e)
{
if (isLoadingSelection)
{
return;
}
UpdatePreview();
if (selectedItem is not null)
{
StatusText.Text = "Click Save to keep the selected item launch options.";
}
}
private void EnsureAutomaticIconPath() private void EnsureAutomaticIconPath()
{ {
if (TypeBox.SelectedItem is LauncherItemType.Menu) if (TypeBox.SelectedItem is LauncherItemType.Menu)
@@ -1857,9 +1935,18 @@ public partial class MainWindow : Window
Type = TypeBox.SelectedItem is LauncherItemType type ? type : LauncherItemType.App, Type = TypeBox.SelectedItem is LauncherItemType type ? type : LauncherItemType.App,
Target = TargetBox.Text.Trim(), Target = TargetBox.Text.Trim(),
Arguments = string.IsNullOrWhiteSpace(ArgumentsBox.Text) ? null : ArgumentsBox.Text.Trim(), Arguments = string.IsNullOrWhiteSpace(ArgumentsBox.Text) ? null : ArgumentsBox.Text.Trim(),
WorkingFolder = LauncherController.CanUseLaunchOptions(TypeBox.SelectedItem is LauncherItemType itemType ? itemType : LauncherItemType.App) &&
!string.IsNullOrWhiteSpace(WorkingFolderBox.Text)
? WorkingFolderBox.Text.Trim()
: null,
Notes = string.IsNullOrWhiteSpace(NotesBox.Text) ? null : NotesBox.Text.Trim(),
IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(), IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(),
IsFavorite = selectedItem.IsFavorite, IsFavorite = selectedItem.IsFavorite,
IsDisabled = selectedItem.IsDisabled, IsDisabled = selectedItem.IsDisabled,
RunAsAdmin = LauncherController.CanRunAsAdmin(TypeBox.SelectedItem is LauncherItemType adminType ? adminType : LauncherItemType.App) &&
RunAsAdminBox.IsChecked == true,
StartMinimized = LauncherController.CanUseLaunchOptions(TypeBox.SelectedItem is LauncherItemType launchType ? launchType : LauncherItemType.App) &&
StartMinimizedBox.IsChecked == true,
DisplayMode = ItemDisplayModeBox.SelectedItem is LauncherDisplayMode mode DisplayMode = ItemDisplayModeBox.SelectedItem is LauncherDisplayMode mode
? mode ? mode
: selectedItem.DisplayMode : selectedItem.DisplayMode
@@ -1926,12 +2013,30 @@ public partial class MainWindow : Window
private void UpdateTargetAvailability() private void UpdateTargetAvailability()
{ {
bool isMenu = TypeBox.SelectedItem is LauncherItemType.Menu; bool isMenu = TypeBox.SelectedItem is LauncherItemType.Menu;
bool canUseLaunchOptions = TypeBox.SelectedItem is LauncherItemType launchType && LauncherController.CanUseLaunchOptions(launchType);
bool canRunAsAdmin = TypeBox.SelectedItem is LauncherItemType itemType && LauncherController.CanRunAsAdmin(itemType);
TargetBox.IsEnabled = !isMenu; TargetBox.IsEnabled = !isMenu;
ArgumentsBox.IsEnabled = !isMenu; ArgumentsBox.IsEnabled = !isMenu;
WorkingFolderBox.IsEnabled = canUseLaunchOptions;
WorkingFolderBrowseButton.IsEnabled = canUseLaunchOptions;
RunAsAdminBox.IsEnabled = canRunAsAdmin;
StartMinimizedBox.IsEnabled = canUseLaunchOptions;
if (isMenu && !isLoadingSelection) if (isMenu && !isLoadingSelection)
{ {
TargetBox.Text = ""; TargetBox.Text = "";
ArgumentsBox.Text = ""; ArgumentsBox.Text = "";
WorkingFolderBox.Text = "";
}
if (!canRunAsAdmin && !isLoadingSelection)
{
RunAsAdminBox.IsChecked = false;
}
if (!canUseLaunchOptions && !isLoadingSelection)
{
WorkingFolderBox.Text = "";
StartMinimizedBox.IsChecked = false;
} }
} }

View File

@@ -23,12 +23,20 @@ public sealed class LauncherItem
public string? Arguments { get; set; } public string? Arguments { get; set; }
public string? WorkingFolder { get; set; }
public string? Notes { get; set; }
public string? IconPath { get; set; } public string? IconPath { get; set; }
public bool IsFavorite { get; set; } public bool IsFavorite { get; set; }
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }
public bool RunAsAdmin { get; set; }
public bool StartMinimized { get; set; }
public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList; public LauncherDisplayMode DisplayMode { get; set; } = LauncherDisplayMode.CompactList;
public List<LauncherItem> Children { get; set; } = []; public List<LauncherItem> Children { get; set; } = [];

View File

@@ -8,17 +8,24 @@ Download the compiled Windows release from:
[Taskbar Launcher Releases](https://git.mget.ca/MGET/Taskbar-Launcher/releases) [Taskbar Launcher Releases](https://git.mget.ca/MGET/Taskbar-Launcher/releases)
Current stable release: `v1.0.2`
## Features ## Features
- System tray launcher - System tray launcher
- Global hotkey, default `Ctrl+Alt+Space` - Global hotkey, default `Ctrl+Alt+Space`
- Search/filter in the launcher popup with parent menu context - Search/filter in the launcher popup with parent menu context
- Keyboard navigation in the launcher popup
- Tooltips for launcher items, menu paths, targets, and arguments
- Recent items section for quickly relaunching entries - Recent items section for quickly relaunching entries
- Nested menus, drag/drop ordering, and drag/drop item creation in Settings - Nested menus, drag/drop ordering, and drag/drop item creation in Settings
- Right-click launcher entries to edit them in Settings - Right-click launcher entries to edit them in Settings
- Right-click launcher entries to pin or unpin them from the top - Right-click launcher entries to pin or unpin them from the top
- Right-click launcher entries or Settings items to temporarily disable them - Right-click launcher entries or Settings items to temporarily disable them
- Right-click app/file entries to open their containing folder - Right-click app/file entries to open their containing folder
- Optional run-as-administrator mode for app and file entries
- Optional working folder and start-minimized launch options for app and file entries
- Per-item notes, visible in launcher tooltips and searchable from the popup
- Validate missing app, file, and folder targets from Settings - Validate missing app, file, and folder targets from Settings
- Export and import selected items or menu sections - Export and import selected items or menu sections
- Sort a selected menu or item level alphabetically - Sort a selected menu or item level alphabetically
@@ -31,6 +38,10 @@ Download the compiled Windows release from:
- Optional Start with Windows shortcut - Optional Start with Windows shortcut
- Windows light/dark theme support - Windows light/dark theme support
## Latest Stable
`v1.0.2` adds the newer launcher management and polish work from the development builds, including recent items, pinned items, drag/drop item creation, item duplication, item notes, target validation, selected item import/export, run-as-administrator support, working folders, start-minimized launch options, launcher tooltips, and the refreshed Settings layout.
## Screenshots ## Screenshots
### Launcher Popup ### Launcher Popup
@@ -83,5 +94,5 @@ Recommended release flow for maintainers:
1. Update `<Version>` in `TaskbarLauncher.csproj`. 1. Update `<Version>` in `TaskbarLauncher.csproj`.
2. Commit the change. 2. Commit the change.
3. Build a release package locally. 3. Build a release package locally.
4. Tag the release, for example `v1.0.1`. 4. Tag the release, for example `v1.0.2`.
5. Upload the release zip to Gitea Releases. 5. Upload the release zip to Gitea Releases.

View File

@@ -180,9 +180,13 @@ public sealed class LauncherController : IDisposable
Type = item.Type, Type = item.Type,
Target = item.Target, Target = item.Target,
Arguments = item.Arguments, Arguments = item.Arguments,
WorkingFolder = item.WorkingFolder,
Notes = item.Notes,
IconPath = item.IconPath, IconPath = item.IconPath,
IsFavorite = false, IsFavorite = false,
IsDisabled = false, IsDisabled = false,
RunAsAdmin = item.RunAsAdmin,
StartMinimized = item.StartMinimized,
DisplayMode = item.DisplayMode, DisplayMode = item.DisplayMode,
Children = [] Children = []
}; };
@@ -275,17 +279,30 @@ public sealed class LauncherController : IDisposable
} }
string target = Environment.ExpandEnvironmentVariables(item.Target); string target = Environment.ExpandEnvironmentVariables(item.Target);
bool runAsAdmin = item.RunAsAdmin && CanRunAsAdmin(item.Type);
string launchTarget = runAsAdmin ? ResolveExecutableTarget(target) : target;
string workingDirectory = GetWorkingDirectory(item, target);
try try
{ {
ProcessStartInfo startInfo = new() ProcessStartInfo startInfo = new()
{ {
FileName = target, FileName = launchTarget,
Arguments = item.Arguments ?? "", Arguments = item.Arguments ?? "",
UseShellExecute = true, UseShellExecute = true,
WorkingDirectory = Directory.Exists(target) ? target : ConfigService.AppFolder WorkingDirectory = workingDirectory
}; };
if (runAsAdmin)
{
startInfo.Verb = "runas";
}
if (item.StartMinimized && CanUseLaunchOptions(item.Type))
{
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
}
Process.Start(startInfo); Process.Start(startInfo);
return true; return true;
} }
@@ -295,4 +312,56 @@ public sealed class LauncherController : IDisposable
return false; return false;
} }
} }
public static bool CanRunAsAdmin(LauncherItemType itemType)
{
return itemType is LauncherItemType.App or LauncherItemType.File;
}
public static bool CanUseLaunchOptions(LauncherItemType itemType)
{
return itemType is LauncherItemType.App or LauncherItemType.File;
}
private static string GetWorkingDirectory(LauncherItem item, string target)
{
if (CanUseLaunchOptions(item.Type) && !string.IsNullOrWhiteSpace(item.WorkingFolder))
{
string configuredFolder = Environment.ExpandEnvironmentVariables(item.WorkingFolder.Trim());
if (Directory.Exists(configuredFolder))
{
return configuredFolder;
}
}
return Directory.Exists(target) ? target : ConfigService.AppFolder;
}
private static string ResolveExecutableTarget(string target)
{
if (Path.IsPathRooted(target) || Path.GetDirectoryName(target) is { Length: > 0 })
{
return target;
}
string[] searchFolders =
[
Environment.CurrentDirectory,
Environment.GetFolderPath(Environment.SpecialFolder.Windows),
Environment.GetFolderPath(Environment.SpecialFolder.System),
.. (Environment.GetEnvironmentVariable("PATH") ?? "")
.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
];
foreach (string folder in searchFolders.Where(Directory.Exists).Distinct(StringComparer.OrdinalIgnoreCase))
{
string candidate = Path.Combine(folder, target);
if (File.Exists(candidate))
{
return candidate;
}
}
return target;
}
} }

View File

@@ -11,10 +11,10 @@
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Authors>Ray Berezowski</Authors> <Authors>Ray Berezowski</Authors>
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon> <ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
<Version>1.0.1</Version> <Version>1.0.2</Version>
<AssemblyVersion>1.0.1.14</AssemblyVersion> <AssemblyVersion>1.0.2.0</AssemblyVersion>
<FileVersion>1.0.1.14</FileVersion> <FileVersion>1.0.2.0</FileVersion>
<InformationalVersion>1.0.1</InformationalVersion> <InformationalVersion>1.0.2</InformationalVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -14,6 +14,7 @@ public partial class LauncherPopup : Window
private const double HeaderAndChromeHeight = 118; private const double HeaderAndChromeHeight = 118;
private bool isRepositionQueued; private bool isRepositionQueued;
private readonly List<System.Windows.Controls.Button> visibleLaunchButtons = []; private readonly List<System.Windows.Controls.Button> visibleLaunchButtons = [];
private readonly List<System.Windows.Controls.Control> visibleNavigationControls = [];
private readonly LauncherConfig config; private readonly LauncherConfig config;
private readonly Action<LauncherItem> launchItem; private readonly Action<LauncherItem> launchItem;
@@ -96,6 +97,7 @@ public partial class LauncherPopup : Window
{ {
ItemsPanel.Children.Clear(); ItemsPanel.Children.Clear();
visibleLaunchButtons.Clear(); visibleLaunchButtons.Clear();
visibleNavigationControls.Clear();
if (config.RecentItems.Any(item => !item.IsDisabled)) if (config.RecentItems.Any(item => !item.IsDisabled))
{ {
@@ -107,6 +109,8 @@ public partial class LauncherPopup : Window
{ {
ItemsPanel.Children.Add(CreateItemControl(item, 0, expandMenusByDefault)); ItemsPanel.Children.Add(CreateItemControl(item, 0, expandMenusByDefault));
} }
RebuildNavigationLists();
} }
private Expander CreateRecentItemsControl() private Expander CreateRecentItemsControl()
@@ -115,8 +119,12 @@ public partial class LauncherPopup : Window
{ {
Header = new TextBlock { Text = "Recent" }, Header = new TextBlock { Text = "Recent" },
IsExpanded = true, IsExpanded = true,
Margin = new Thickness(0, 4, 0, 8) Margin = new Thickness(0, 4, 0, 8),
Focusable = true
}; };
expander.KeyDown += NavigationControl_KeyDown;
expander.Expanded += (_, _) => RebuildNavigationAndReposition();
expander.Collapsed += (_, _) => RebuildNavigationAndReposition();
StackPanel recentItems = new(); StackPanel recentItems = new();
foreach (LauncherItem item in config.RecentItems.Where(item => !item.IsDisabled)) foreach (LauncherItem item in config.RecentItems.Where(item => !item.IsDisabled))
@@ -136,8 +144,14 @@ public partial class LauncherPopup : Window
{ {
Header = CreateMenuHeader(item), Header = CreateMenuHeader(item),
IsExpanded = expandMenusByDefault, IsExpanded = expandMenusByDefault,
Margin = new Thickness(depth * 12, 4, 0, 4) Margin = new Thickness(depth * 12, 4, 0, 4),
Focusable = true,
Tag = item,
ToolTip = CreateItemToolTip(item)
}; };
expander.KeyDown += NavigationControl_KeyDown;
expander.Expanded += (_, _) => RebuildNavigationAndReposition();
expander.Collapsed += (_, _) => RebuildNavigationAndReposition();
StackPanel children = new(); StackPanel children = new();
foreach (LauncherItem child in GetDisplayItems(item.Children)) foreach (LauncherItem child in GetDisplayItems(item.Children))
@@ -155,7 +169,8 @@ public partial class LauncherPopup : Window
Margin = new Thickness(depth * 12, 2, 0, 2), Margin = new Thickness(depth * 12, 2, 0, 2),
MinHeight = DisplayModeMetrics.GetRowHeight(GetEffectiveDisplayMode(item)), MinHeight = DisplayModeMetrics.GetRowHeight(GetEffectiveDisplayMode(item)),
HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left, HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left,
Tag = item Tag = item,
ToolTip = CreateItemToolTip(item)
}; };
button.Click += (_, _) => launchItem(item); button.Click += (_, _) => launchItem(item);
@@ -164,20 +179,7 @@ public partial class LauncherPopup : Window
button.ContextMenu = CreateItemContextMenu(item); button.ContextMenu = CreateItemContextMenu(item);
} }
button.KeyDown += (_, e) => button.KeyDown += LaunchButton_KeyDown;
{
if (e.Key == Key.Enter)
{
launchItem(item);
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
Close();
e.Handled = true;
}
};
visibleLaunchButtons.Add(button);
return button; return button;
} }
@@ -204,7 +206,8 @@ public partial class LauncherPopup : Window
return new TextBlock return new TextBlock
{ {
Text = item.Title, Text = item.Title,
ContextMenu = contextMenu ContextMenu = contextMenu,
ToolTip = CreateItemToolTip(item)
}; };
} }
@@ -213,6 +216,7 @@ public partial class LauncherPopup : Window
System.Windows.Controls.ContextMenu contextMenu = new(); System.Windows.Controls.ContextMenu contextMenu = new();
contextMenu.Items.Add(CreatePinMenuItem(item)); contextMenu.Items.Add(CreatePinMenuItem(item));
contextMenu.Items.Add(CreateDisableMenuItem(item)); contextMenu.Items.Add(CreateDisableMenuItem(item));
contextMenu.Items.Add(CreateRunAsAdminMenuItem(item));
contextMenu.Items.Add(CreateOpenContainingFolderMenuItem(item)); contextMenu.Items.Add(CreateOpenContainingFolderMenuItem(item));
contextMenu.Items.Add(CreateEditMenuItem(item)); contextMenu.Items.Add(CreateEditMenuItem(item));
return contextMenu; return contextMenu;
@@ -254,6 +258,25 @@ public partial class LauncherPopup : Window
return disableMenuItem; return disableMenuItem;
} }
private System.Windows.Controls.MenuItem CreateRunAsAdminMenuItem(LauncherItem item)
{
System.Windows.Controls.MenuItem runAsAdminMenuItem = new()
{
Header = item.RunAsAdmin ? "Run as Admin: On" : "Run as Admin: Off",
IsEnabled = LauncherController.CanRunAsAdmin(item.Type)
};
runAsAdminMenuItem.Click += (_, e) =>
{
item.RunAsAdmin = !item.RunAsAdmin;
ConfigService.Save(config);
BuildItems();
QueueReposition();
e.Handled = true;
};
return runAsAdminMenuItem;
}
private System.Windows.Controls.MenuItem CreateOpenContainingFolderMenuItem(LauncherItem item) private System.Windows.Controls.MenuItem CreateOpenContainingFolderMenuItem(LauncherItem item)
{ {
System.Windows.Controls.MenuItem openFolderMenuItem = new() System.Windows.Controls.MenuItem openFolderMenuItem = new()
@@ -341,6 +364,8 @@ public partial class LauncherPopup : Window
{ {
ItemsPanel.Children.Add(CreateSearchResultControl(match)); ItemsPanel.Children.Add(CreateSearchResultControl(match));
} }
RebuildNavigationLists();
} }
private FrameworkElement CreateSearchResultControl(SearchMatch match) private FrameworkElement CreateSearchResultControl(SearchMatch match)
@@ -352,28 +377,56 @@ public partial class LauncherPopup : Window
MinHeight = Math.Max(DisplayModeMetrics.GetRowHeight(GetEffectiveDisplayMode(match.Item)), 48), MinHeight = Math.Max(DisplayModeMetrics.GetRowHeight(GetEffectiveDisplayMode(match.Item)), 48),
HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left, HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left,
Tag = match.Item, Tag = match.Item,
ContextMenu = CreateItemContextMenu(match.Item) ContextMenu = CreateItemContextMenu(match.Item),
ToolTip = CreateItemToolTip(match.Item, match.ParentPath)
}; };
button.Click += (_, _) => launchItem(match.Item); button.Click += (_, _) => launchItem(match.Item);
button.KeyDown += (_, e) => button.KeyDown += LaunchButton_KeyDown;
{
if (e.Key == Key.Enter)
{
launchItem(match.Item);
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
Close();
e.Handled = true;
}
};
visibleLaunchButtons.Add(button);
return button; return button;
} }
private void RebuildNavigationAndReposition()
{
RebuildNavigationLists();
QueueReposition();
}
private void RebuildNavigationLists()
{
visibleLaunchButtons.Clear();
visibleNavigationControls.Clear();
AddVisibleNavigationControls(ItemsPanel.Children);
}
private void AddVisibleNavigationControls(UIElementCollection elements)
{
foreach (UIElement element in elements)
{
switch (element)
{
case Expander expander:
visibleNavigationControls.Add(expander);
if (expander.IsExpanded && expander.Content is System.Windows.Controls.Panel panel)
{
AddVisibleNavigationControls(panel.Children);
}
break;
case System.Windows.Controls.Button button:
visibleNavigationControls.Add(button);
if (button.Tag is LauncherItem)
{
visibleLaunchButtons.Add(button);
}
break;
}
}
}
private object CreateSearchResultContent(SearchMatch match) private object CreateSearchResultContent(SearchMatch match)
{ {
StackPanel panel = new(); StackPanel panel = new();
@@ -394,6 +447,66 @@ public partial class LauncherPopup : Window
return panel; return panel;
} }
private static System.Windows.Controls.ToolTip CreateItemToolTip(LauncherItem item, string? parentPath = null)
{
StackPanel panel = new() { MaxWidth = 520 };
AddToolTipLine(panel, item.Title, bold: true);
AddToolTipLine(panel, $"Type: {item.Type}");
if (!string.IsNullOrWhiteSpace(parentPath))
{
AddToolTipLine(panel, $"Menu: {parentPath}");
}
if (!string.IsNullOrWhiteSpace(item.Target))
{
AddToolTipLine(panel, $"Target: {item.Target}");
}
if (!string.IsNullOrWhiteSpace(item.Arguments))
{
AddToolTipLine(panel, $"Arguments: {item.Arguments}");
}
if (!string.IsNullOrWhiteSpace(item.WorkingFolder))
{
AddToolTipLine(panel, $"Working folder: {item.WorkingFolder}");
}
if (!string.IsNullOrWhiteSpace(item.Notes))
{
AddToolTipLine(panel, $"Notes: {item.Notes}");
}
if (item.IsFavorite)
{
AddToolTipLine(panel, "Pinned to top");
}
if (item.RunAsAdmin)
{
AddToolTipLine(panel, "Runs as administrator");
}
if (item.StartMinimized)
{
AddToolTipLine(panel, "Starts minimized");
}
return new System.Windows.Controls.ToolTip { Content = panel };
}
private static void AddToolTipLine(StackPanel panel, string text, bool bold = false)
{
panel.Children.Add(new TextBlock
{
Text = text,
FontWeight = bold ? FontWeights.SemiBold : FontWeights.Normal,
TextWrapping = TextWrapping.Wrap,
Margin = panel.Children.Count == 0 ? new Thickness(0) : new Thickness(0, 3, 0, 0)
});
}
private static void CollectMatches(IEnumerable<LauncherItem> items, string query, List<string> parentNames, List<SearchMatch> matches) private static void CollectMatches(IEnumerable<LauncherItem> items, string query, List<string> parentNames, List<SearchMatch> matches)
{ {
foreach (LauncherItem item in GetDisplayItems(items)) foreach (LauncherItem item in GetDisplayItems(items))
@@ -418,6 +531,7 @@ public partial class LauncherPopup : Window
return item.Title.Contains(query, StringComparison.OrdinalIgnoreCase) || return item.Title.Contains(query, StringComparison.OrdinalIgnoreCase) ||
item.Target.Contains(query, StringComparison.OrdinalIgnoreCase) || item.Target.Contains(query, StringComparison.OrdinalIgnoreCase) ||
(item.Arguments?.Contains(query, StringComparison.OrdinalIgnoreCase) ?? false) || (item.Arguments?.Contains(query, StringComparison.OrdinalIgnoreCase) ?? false) ||
(item.Notes?.Contains(query, StringComparison.OrdinalIgnoreCase) ?? false) ||
parentPath.Contains(query, StringComparison.OrdinalIgnoreCase); parentPath.Contains(query, StringComparison.OrdinalIgnoreCase);
} }
@@ -437,14 +551,142 @@ public partial class LauncherPopup : Window
return; return;
} }
if (e.Key == Key.Down && visibleLaunchButtons.FirstOrDefault() is System.Windows.Controls.Button firstButton) if (e.Key == Key.Down)
{ {
Keyboard.ClearFocus(); FocusNavigationControl(0);
firstButton.Focus(); e.Handled = true;
return;
}
if (e.Key == Key.Up)
{
FocusNavigationControl(visibleNavigationControls.Count - 1);
e.Handled = true;
return;
}
if (e.Key == Key.Home)
{
SearchBox.CaretIndex = 0;
e.Handled = true;
return;
}
if (e.Key == Key.End)
{
SearchBox.CaretIndex = SearchBox.Text.Length;
e.Handled = true; e.Handled = true;
} }
} }
private void LaunchButton_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (sender is not System.Windows.Controls.Button button)
{
return;
}
if (e.Key == Key.Enter && button.Tag is LauncherItem item)
{
launchItem(item);
e.Handled = true;
return;
}
if (e.Key == Key.Escape)
{
Close();
e.Handled = true;
return;
}
HandleNavigationControlKey(button, e);
}
private void NavigationControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (sender is not System.Windows.Controls.Control control)
{
return;
}
if (e.Key == Key.Enter && control is Expander expander)
{
expander.IsExpanded = !expander.IsExpanded;
QueueReposition();
e.Handled = true;
return;
}
if (e.Key == Key.Escape)
{
Close();
e.Handled = true;
return;
}
HandleNavigationControlKey(control, e);
}
private void HandleNavigationControlKey(System.Windows.Controls.Control control, System.Windows.Input.KeyEventArgs e)
{
int index = visibleNavigationControls.IndexOf(control);
if (index < 0)
{
return;
}
if (e.Key == Key.Down)
{
FocusNavigationControl(index + 1);
e.Handled = true;
return;
}
if (e.Key == Key.Up)
{
if (index == 0)
{
SearchBox.Focus();
SearchBox.CaretIndex = SearchBox.Text.Length;
}
else
{
FocusNavigationControl(index - 1);
}
e.Handled = true;
return;
}
if (e.Key == Key.Home)
{
FocusNavigationControl(0);
e.Handled = true;
return;
}
if (e.Key == Key.End)
{
FocusNavigationControl(visibleNavigationControls.Count - 1);
e.Handled = true;
}
}
private void FocusNavigationControl(int index)
{
if (visibleNavigationControls.Count == 0)
{
return;
}
int clampedIndex = Math.Clamp(index, 0, visibleNavigationControls.Count - 1);
System.Windows.Controls.Control control = visibleNavigationControls[clampedIndex];
Keyboard.ClearFocus();
control.Focus();
control.BringIntoView();
}
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
{ {
if (e.Key == Key.Escape) if (e.Key == Key.Escape)

View File

@@ -9,9 +9,13 @@
"Type": "Menu", "Type": "Menu",
"Target": "", "Target": "",
"Arguments": null, "Arguments": null,
"WorkingFolder": null,
"Notes": null,
"IconPath": null, "IconPath": null,
"IsFavorite": false, "IsFavorite": false,
"IsDisabled": false, "IsDisabled": false,
"RunAsAdmin": false,
"StartMinimized": false,
"DisplayMode": "CompactList", "DisplayMode": "CompactList",
"Children": [ "Children": [
{ {
@@ -19,9 +23,13 @@
"Type": "Folder", "Type": "Folder",
"Target": "%USERPROFILE%\\Documents", "Target": "%USERPROFILE%\\Documents",
"Arguments": null, "Arguments": null,
"WorkingFolder": null,
"Notes": null,
"IconPath": "%USERPROFILE%\\Documents", "IconPath": "%USERPROFILE%\\Documents",
"IsFavorite": false, "IsFavorite": false,
"IsDisabled": false, "IsDisabled": false,
"RunAsAdmin": false,
"StartMinimized": false,
"DisplayMode": "CompactList", "DisplayMode": "CompactList",
"Children": [] "Children": []
}, },
@@ -30,9 +38,13 @@
"Type": "Folder", "Type": "Folder",
"Target": "%USERPROFILE%\\Downloads", "Target": "%USERPROFILE%\\Downloads",
"Arguments": null, "Arguments": null,
"WorkingFolder": null,
"Notes": null,
"IconPath": "%USERPROFILE%\\Downloads", "IconPath": "%USERPROFILE%\\Downloads",
"IsFavorite": false, "IsFavorite": false,
"IsDisabled": false, "IsDisabled": false,
"RunAsAdmin": false,
"StartMinimized": false,
"DisplayMode": "CompactList", "DisplayMode": "CompactList",
"Children": [] "Children": []
} }
@@ -43,9 +55,13 @@
"Type": "Menu", "Type": "Menu",
"Target": "", "Target": "",
"Arguments": null, "Arguments": null,
"WorkingFolder": null,
"Notes": null,
"IconPath": null, "IconPath": null,
"IsFavorite": false, "IsFavorite": false,
"IsDisabled": false, "IsDisabled": false,
"RunAsAdmin": false,
"StartMinimized": false,
"DisplayMode": "CompactList", "DisplayMode": "CompactList",
"Children": [ "Children": [
{ {
@@ -53,9 +69,13 @@
"Type": "App", "Type": "App",
"Target": "notepad.exe", "Target": "notepad.exe",
"Arguments": null, "Arguments": null,
"WorkingFolder": null,
"Notes": "Simple starter app entry.",
"IconPath": "notepad.exe", "IconPath": "notepad.exe",
"IsFavorite": false, "IsFavorite": false,
"IsDisabled": false, "IsDisabled": false,
"RunAsAdmin": false,
"StartMinimized": false,
"DisplayMode": "CompactList", "DisplayMode": "CompactList",
"Children": [] "Children": []
} }

View File

@@ -1,5 +1,7 @@
# Install and Use Taskbar Launcher # Install and Use Taskbar Launcher
Current stable release: `v1.0.2`
## Small Package ## Small Package
The small package requires: The small package requires:
@@ -28,6 +30,10 @@ Recently launched entries appear in the launcher popup under **Recent**.
Search results include the parent menu path when an item is inside a menu. Search results include the parent menu path when an item is inside a menu.
Use arrow keys, Home, End, Enter, and Escape to navigate the launcher popup from the keyboard.
Hover over launcher items to see details such as type, menu path, target, and arguments.
## Configure ## Configure
Open the tray menu and choose **Settings**. Open the tray menu and choose **Settings**.
@@ -43,6 +49,9 @@ From Settings you can:
- Right-click an item and choose **Pin to Top** to show it first in its menu - Right-click an item and choose **Pin to Top** to show it first in its menu
- Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it - Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it
- Right-click an app or file item and choose **Open Containing Folder** - Right-click an app or file item and choose **Open Containing Folder**
- Enable **Run as administrator** for app and file items that need elevation
- Set a working folder or start apps minimized for app and file items
- Add notes to items for reminders, context, or search keywords
- Use **Config... > Validate Targets** to check for missing app, file, and folder targets - Use **Config... > Validate Targets** to check for missing app, file, and folder targets
- Use **Config... > Export Selected Item** or **Import Item** to share one item or menu section - Use **Config... > Export Selected Item** or **Import Item** to share one item or menu section
- Right-click an item and choose **Sort This Menu** or **Sort This Level** - Right-click an item and choose **Sort This Menu** or **Sort This Level**

View File

@@ -21,6 +21,7 @@ Examples:
- `1.0.0`: first stable release - `1.0.0`: first stable release
- `1.0.1`: bug fix - `1.0.1`: bug fix
- `1.0.2`: tested feature and polish release
- `1.1.0`: new feature - `1.1.0`: new feature
Update version fields in: Update version fields in:
@@ -37,9 +38,9 @@ TaskbarLauncher.csproj
4. Tag the release: 4. Tag the release:
```powershell ```powershell
git tag v1.0.0 git tag v1.0.2
git push origin main git push origin main
git push origin v1.0.0 git push origin v1.0.2
``` ```
5. In Gitea, create a release from the tag. 5. In Gitea, create a release from the tag.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 55 KiB