From 17f92df25afd41e2f8354217f003f7ac7722bd01 Mon Sep 17 00:00:00 2001 From: Chris Titus Tech Date: Thu, 5 Feb 2026 15:03:36 -0600 Subject: [PATCH] Make collapsable categories --- .../private/Find-AppsByNameOrDescription.ps1 | 35 +++++++- .../private/Initialize-InstallAppArea.ps1 | 8 +- .../Initialize-InstallCategoryAppList.ps1 | 85 +++++++++++++------ xaml/inputXML.xaml | 4 +- 4 files changed, 99 insertions(+), 33 deletions(-) diff --git a/functions/private/Find-AppsByNameOrDescription.ps1 b/functions/private/Find-AppsByNameOrDescription.ps1 index 3300d88b..97203780 100644 --- a/functions/private/Find-AppsByNameOrDescription.ps1 +++ b/functions/private/Find-AppsByNameOrDescription.ps1 @@ -16,7 +16,25 @@ function Find-AppsByNameOrDescription { $_.Visibility = [Windows.Visibility]::Visible $_.Children | ForEach-Object { if ($null -ne $_) { - $_.Visibility = [Windows.Visibility]::Visible + # Respect the collapsed state of categories (indicated by + prefix) + if ($_.Tag -like "CategoryToggleButton" -and $_.Content -like "+*") { + # Keep category label visible but don't expand the WrapPanel + $_.Visibility = [Windows.Visibility]::Visible + } + elseif ($_.Tag -like "CategoryWrapPanel_*") { + # Check if parent category is collapsed (has + prefix) + $categoryLabel = $_.Parent.Children[0] + if ($categoryLabel.Content -like "+*") { + # Keep collapsed + $_.Visibility = [Windows.Visibility]::Collapsed + } else { + # Expand + $_.Visibility = [Windows.Visibility]::Visible + } + } + else { + $_.Visibility = [Windows.Visibility]::Visible + } } } @@ -32,18 +50,29 @@ function Find-AppsByNameOrDescription { # Hide all CategoryWrapPanel and ToggleButton $_.Visibility = [Windows.Visibility]::Collapsed if ($_.Tag -like "CategoryWrapPanel_*") { + $categoryHasMatch = $false # Search for Apps that match the search string $_.Children | Foreach-Object { $appEntry = $sync.configs.applicationsHashtable.$($_.Tag) if ($appEntry.Content -like "*$SearchString*" -or $appEntry.Description -like "*$SearchString*") { - # Show the App and the parent CategoryWrapPanel if the string is found + # Show the App and mark that this category has a match $_.Visibility = [Windows.Visibility]::Visible - $_.parent.Visibility = [Windows.Visibility]::Visible + $categoryHasMatch = $true } else { $_.Visibility = [Windows.Visibility]::Collapsed } } + + # If category has matches, show the WrapPanel and update the category label to expanded state + if ($categoryHasMatch) { + $_.Visibility = [Windows.Visibility]::Visible + # Update category label to show expanded state (-) + $categoryLabel = $_.Parent.Children[0] + if ($categoryLabel.Content -like "+*") { + $categoryLabel.Content = $categoryLabel.Content -replace "^\+ ", "- " + } + } } } } diff --git a/functions/private/Initialize-InstallAppArea.ps1 b/functions/private/Initialize-InstallAppArea.ps1 index bb605ab8..85bd50a7 100644 --- a/functions/private/Initialize-InstallAppArea.ps1 +++ b/functions/private/Initialize-InstallAppArea.ps1 @@ -41,13 +41,13 @@ $itemsControl.VerticalAlignment = 'Stretch' $scrollViewer.Content = $itemsControl - # Enable virtualization for the ItemsControl to improve performance (It's hard to test if this is actually working, so if you know what you're doing, please check this) + # Use WrapPanel to create dynamic columns based on AppEntryWidth and window width $itemsPanelTemplate = New-Object Windows.Controls.ItemsPanelTemplate - $factory = New-Object Windows.FrameworkElementFactory ([Windows.Controls.VirtualizingStackPanel]) + $factory = New-Object Windows.FrameworkElementFactory ([Windows.Controls.WrapPanel]) + $factory.SetValue([Windows.Controls.WrapPanel]::OrientationProperty, [Windows.Controls.Orientation]::Horizontal) + $factory.SetValue([Windows.Controls.WrapPanel]::HorizontalAlignmentProperty, [Windows.HorizontalAlignment]::Left) $itemsPanelTemplate.VisualTree = $factory $itemsControl.ItemsPanel = $itemsPanelTemplate - $itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::IsVirtualizingProperty, $true) - $itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::VirtualizationModeProperty, [Windows.Controls.VirtualizationMode]::Recycling) # Add the Border containing the App Area to the target Grid $targetGrid.Children.Add($Border) | Out-Null diff --git a/functions/private/Initialize-InstallCategoryAppList.ps1 b/functions/private/Initialize-InstallCategoryAppList.ps1 index 96e39053..6d3c8737 100644 --- a/functions/private/Initialize-InstallCategoryAppList.ps1 +++ b/functions/private/Initialize-InstallCategoryAppList.ps1 @@ -15,22 +15,6 @@ function Initialize-InstallCategoryAppList { $TargetElement, $Apps ) - function Add-Category { - param( - [string]$Category, - [Windows.Controls.ItemsControl]$TargetElement - ) - - $toggleButton = New-Object Windows.Controls.Label - $toggleButton.Content = "$Category" - $toggleButton.Tag = "CategoryToggleButton" - $toggleButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "HeaderFontSize") - $toggleButton.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") - $sync.$Category = $toggleButton - - $null = $TargetElement.Items.Add($toggleButton) - } - # Pre-group apps by category $appsByCategory = @{} @@ -42,17 +26,70 @@ function Initialize-InstallCategoryAppList { $appsByCategory[$category] += $appKey } foreach ($category in $($appsByCategory.Keys | Sort-Object)) { - Add-Category -Category $category -TargetElement $TargetElement + # Create a container for category label + apps + $categoryContainer = New-Object Windows.Controls.StackPanel + $categoryContainer.Orientation = "Vertical" + $categoryContainer.Margin = New-Object Windows.Thickness(0, 0, 0, 0) + $categoryContainer.HorizontalAlignment = [Windows.HorizontalAlignment]::Stretch + + # Bind Width to the ItemsControl's ActualWidth to force full-row layout in WrapPanel + $binding = New-Object Windows.Data.Binding + $binding.Path = New-Object Windows.PropertyPath("ActualWidth") + $binding.RelativeSource = New-Object Windows.Data.RelativeSource([Windows.Data.RelativeSourceMode]::FindAncestor, [Windows.Controls.ItemsControl], 1) + [void][Windows.Data.BindingOperations]::SetBinding($categoryContainer, [Windows.FrameworkElement]::WidthProperty, $binding) + + # Add category label to container + $toggleButton = New-Object Windows.Controls.Label + $toggleButton.Content = "- $Category" + $toggleButton.Tag = "CategoryToggleButton" + $toggleButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "HeaderFontSize") + $toggleButton.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") + $toggleButton.Cursor = [System.Windows.Input.Cursors]::Hand + $toggleButton.HorizontalAlignment = [Windows.HorizontalAlignment]::Stretch + $sync.$Category = $toggleButton + + # Add click handler to toggle category visibility + $toggleButton.Add_MouseLeftButtonUp({ + param($sender, $e) + + # Find the parent StackPanel (categoryContainer) + $categoryContainer = $sender.Parent + if ($categoryContainer -and $categoryContainer.Children.Count -ge 2) { + # The WrapPanel is the second child + $wrapPanel = $categoryContainer.Children[1] + + # Toggle visibility + if ($wrapPanel.Visibility -eq [Windows.Visibility]::Visible) { + $wrapPanel.Visibility = [Windows.Visibility]::Collapsed + # Change - to + + $sender.Content = $sender.Content -replace "^- ", "+ " + } else { + $wrapPanel.Visibility = [Windows.Visibility]::Visible + # Change + to - + $sender.Content = $sender.Content -replace "^\+ ", "- " + } + } + }) + + $null = $categoryContainer.Children.Add($toggleButton) + + # Add wrap panel for apps to container $wrapPanel = New-Object Windows.Controls.WrapPanel - $wrapPanel.Orientation = "Vertical" - $wrapPanel.HorizontalAlignment = "Stretch" - $wrapPanel.VerticalAlignment = "Center" - $wrapPanel.Margin = New-Object Windows.Thickness(0, 0, 0, 20) + $wrapPanel.Orientation = "Horizontal" + $wrapPanel.HorizontalAlignment = "Left" + $wrapPanel.VerticalAlignment = "Top" + $wrapPanel.Margin = New-Object Windows.Thickness(0, 0, 0, 0) $wrapPanel.Visibility = [Windows.Visibility]::Visible $wrapPanel.Tag = "CategoryWrapPanel_$category" - $null = $TargetElement.Items.Add($wrapPanel) - $appsByCategory[$category] |Sort-Object | ForEach-Object { - $sync.$_ = $(Initialize-InstallAppEntry -TargetElement $wrapPanel -AppKey $_) + + $null = $categoryContainer.Children.Add($wrapPanel) + + # Add the entire category container to the target element + $null = $TargetElement.Items.Add($categoryContainer) + + # Add apps to the wrap panel + $appsByCategory[$category] | Sort-Object | ForEach-Object { + $sync.$_ = $(Initialize-InstallAppEntry -TargetElement $wrapPanel -AppKey $_) } } } diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index 60aa8346..b9db5655 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -107,8 +107,8 @@ BorderBrush="{DynamicResource MainForegroundColor}" Background="{DynamicResource ButtonBackgroundColor}" BorderThickness="1" - Width="16" - Height="16" + Width="12" + Height="12" CornerRadius="2"/>