Add category search chips to Install tab for improved filtering (#4826)

* Add Quick Category Search Chips to Install tab

Add category shortcut chips on the Install tab that insert the category
name into the existing search box. Append each app's category as a
description tag so the existing description search filters by category.
Reuses the standard button style and existing search; no search, install,
or preset logic is changed.

* Fix install category chip filtering

* Fix category filter refresh behavior

---------

Co-authored-by: Chris Titus <contact@christitus.com>
This commit is contained in:
FourSeven
2026-07-15 20:54:22 -05:00
committed by GitHub
co-authored by Chris Titus
parent 975616b58f
commit 51cb3811dd
8 changed files with 107 additions and 9 deletions
@@ -10,6 +10,9 @@ function Find-AppsByNameOrDescription {
.PARAMETER SearchString
The string to be searched for. Wildcards are treated as literal characters.
.PARAMETER Category
When provided, only applications in this exact category are shown.
.NOTES
- Uses module-scope $sync (no parameter needed; inherits from caller's scope)
- Performs literal matching (no wildcard expansion)
@@ -18,7 +21,10 @@ function Find-AppsByNameOrDescription {
#>
param(
[Parameter(Mandatory = $false)]
[string]$SearchString = ""
[string]$SearchString = "",
[Parameter(Mandatory = $false)]
[string]$Category = ""
)
# Validate that $sync exists and has required structure
@@ -39,7 +45,7 @@ function Find-AppsByNameOrDescription {
try {
# Reset the visibility if the search string is empty or the search is cleared
if ([string]::IsNullOrWhiteSpace($SearchString)) {
if ([string]::IsNullOrWhiteSpace($SearchString) -and [string]::IsNullOrWhiteSpace($Category)) {
$sync.ItemsControl.Items | ForEach-Object {
# Each item is a StackPanel container
$_.Visibility = [Windows.Visibility]::Visible
@@ -94,10 +100,11 @@ function Find-AppsByNameOrDescription {
# Check if app matches search criteria
if ($null -ne $appEntry) {
$contentMatch = $appEntry.Content -like "*$escapedSearchString*"
$descriptionMatch = $appEntry.Description -like "*$escapedSearchString*"
$categoryMatch = -not [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Category -eq $Category
$contentMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Content -like "*$escapedSearchString*"
$descriptionMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Description -like "*$escapedSearchString*"
if ($contentMatch -or $descriptionMatch) {
if ($categoryMatch -or $contentMatch -or $descriptionMatch) {
# Show the App and mark that this category has a match
$appControl.Visibility = [Windows.Visibility]::Visible
$categoryHasMatch = $true
@@ -0,0 +1,17 @@
function Set-WinUtilAppCategoryFilter {
<#
.SYNOPSIS
Applies an exact application category filter from an Install tab search chip.
.PARAMETER Category
The application category to show. An empty value clears the filter.
#>
param(
[Parameter(Mandatory = $false)]
[string]$Category = ""
)
$sync.SearchBar.Tag = $Category
$sync.SearchBar.Text = $Category
Find-AppsByNameOrDescription -SearchString $Category -Category $Category
}
@@ -9,7 +9,7 @@ function Invoke-WinUtilInstallAppRenderBatch {
}
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
}
}
+2 -1
View File
@@ -21,6 +21,7 @@ Describe "Install app rendering startup contract" {
$renderScript | Should -Match 'Dispatcher\.BeginInvoke'
$renderScript | Should -Match 'Invoke-WinUtilInstallAppRenderNextBatch'
$renderScript | Should -Match 'Initialize-InstallAppEntry'
$renderScript | Should -Match 'Find-AppsByNameOrDescription -SearchString \$sync\.SearchBar\.Text -Category \$sync\.SearchBar\.Tag'
$renderScript | Should -Match '\$sync\.InstallAppEntriesRendered = \$true'
}
@@ -59,7 +60,7 @@ Describe "Install app rendering startup contract" {
}
function global:Find-AppsByNameOrDescription {
param($SearchString)
param($SearchString, $Category)
throw "Search should not run for an empty search box in this test."
}
+22
View File
@@ -172,18 +172,27 @@ namespace Windows.Controls
WPFInstallBrowser = [pscustomobject]@{
Content = "Firefox"
Description = "Fast private browser"
Category = "Browsers"
}
WPFInstallMedia = [pscustomobject]@{
Content = "VLC"
Description = "Media player"
Category = "Multimedia Tools"
}
WPFInstallLiteral = [pscustomobject]@{
Content = "Tool [abc]"
Description = "Literal wildcard sample"
Category = "Utilities"
}
WPFInstallEditor = [pscustomobject]@{
Content = "Code Editor"
Description = "Text editing"
Category = "Development"
}
WPFInstallPowerToys = [pscustomobject]@{
Content = "PowerToys"
Description = "A collection of system utilities"
Category = "Microsoft Tools"
}
}
}
@@ -363,6 +372,19 @@ Describe "Find-AppsByNameOrDescription" {
$mediaItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
$category.Visibility | Should -Be ([Windows.Visibility]::Visible)
}
It "filters category chips by exact application category" {
$utilityItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral"
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($utilityItem, $powerToysItem)
New-WinUtilAppSearchContext -Categories @($category)
Find-AppsByNameOrDescription -SearchString "Utilities" -Category "Utilities"
$utilityItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
$category.Visibility | Should -Be ([Windows.Visibility]::Visible)
}
}
Describe "Find-TweaksByNameOrDescription" {
+11
View File
@@ -137,6 +137,17 @@ Describe "XAML document" {
"SearchBar",
"SearchBarIcon",
"SearchBarClearButton",
"WPFSearchChips",
"WPFSearchChipAll",
"WPFSearchChipBrowsers",
"WPFSearchChipCommunications",
"WPFSearchChipDevelopment",
"WPFSearchChipGames",
"WPFSearchChipMicrosoftTools",
"WPFSearchChipMultimediaTools",
"WPFSearchChipProTools",
"WPFSearchChipSelfhostedTools",
"WPFSearchChipUtilities",
"appscategory",
"appspanel",
"tweakspanel",
+23 -1
View File
@@ -341,7 +341,7 @@ $searchBarTimer.add_Tick({
$searchBarTimer.Stop()
switch ($sync.currentTab) {
"Install" {
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
}
"Tweaks" {
Find-TweaksByNameOrDescription -SearchString $sync.SearchBar.Text
@@ -352,6 +352,10 @@ $searchBarTimer.add_Tick({
}
})
$sync["SearchBar"].Add_TextChanged({
if ($sync.SearchBar.Tag -ne $sync.SearchBar.Text) {
$sync.SearchBar.Tag = $null
}
if ($sync.SearchBar.Text -ne "") {
$sync.SearchBarClearButton.Visibility = "Visible"
$sync.SearchBarIcon.Visibility = "Collapsed"
@@ -359,12 +363,30 @@ $sync["SearchBar"].Add_TextChanged({
$sync.SearchBarClearButton.Visibility = "Collapsed"
$sync.SearchBarIcon.Visibility = "Visible"
}
# Category chip handlers apply their filter immediately.
if ($sync.SearchBar.Tag -eq $sync.SearchBar.Text) {
return
}
if ($searchBarTimer.IsEnabled) {
$searchBarTimer.Stop()
}
$searchBarTimer.Start()
})
# Quick Category Search Chips
$sync["WPFSearchChipAll"].Add_Click({ Set-WinUtilAppCategoryFilter })
$sync["WPFSearchChipBrowsers"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Browsers" })
$sync["WPFSearchChipCommunications"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Communications" })
$sync["WPFSearchChipDevelopment"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Development" })
$sync["WPFSearchChipGames"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Games" })
$sync["WPFSearchChipMicrosoftTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Microsoft Tools" })
$sync["WPFSearchChipMultimediaTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Multimedia Tools" })
$sync["WPFSearchChipProTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Pro Tools" })
$sync["WPFSearchChipSelfhostedTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Selfhosted Tools" })
$sync["WPFSearchChipUtilities"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Utilities" })
$sync["Form"].Add_Loaded({
param($e)
$null = $e
+19 -1
View File
@@ -1275,8 +1275,26 @@
<TabControl Name="WPFTabNav" Background="Transparent" Width="Auto" Height="Auto" BorderBrush="Transparent" BorderThickness="0" Grid.Row="2" Grid.Column="0" Padding="-1">
<TabItem Header="Install" Visibility="Collapsed" Name="WPFTab1">
<Grid Background="Transparent" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Margin="{DynamicResource TabContentMargin}">
<!-- Quick Category Search Chips -->
<WrapPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5" Name="WPFSearchChips">
<Button Name="WPFSearchChipAll" Content="All" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipBrowsers" Content="Browsers" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipCommunications" Content="Communications" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipDevelopment" Content="Development" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipGames" Content="Games" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipMicrosoftTools" Content="Microsoft Tools" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipMultimediaTools" Content="Multimedia Tools" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipProTools" Content="Pro Tools" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipSelfhostedTools" Content="Selfhosted Tools" Width="Auto" Height="Auto" Margin="2"/>
<Button Name="WPFSearchChipUtilities" Content="Utilities" Width="Auto" Height="Auto" Margin="2"/>
</WrapPanel>
<Grid Grid.Row="1" Margin="{DynamicResource TabContentMargin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />