From ac116d70839be8a9312dbe9202999d2191bee67e Mon Sep 17 00:00:00 2001 From: Chris Titus Date: Fri, 6 Feb 2026 10:26:29 -0600 Subject: [PATCH] New install gui (#3995) * cleanup and checkbox addition * Make collapsable categories * finish new install GUI * Fix search --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/label-pr.yaml | 6 +- config/appnavigation.json | 20 ++++- config/themes.json | 6 +- docs/content/dev/architecture.md | 4 +- .../tweaks/Essential-Tweaks/Hibernation.md | 2 +- .../RemoveOneDrive.md | 2 +- docs/content/faq.md | 2 +- docs/content/userguide/_index.md | 4 +- .../private/Find-AppsByNameOrDescription.ps1 | 61 ++++++++++---- .../private/Initialize-InstallAppArea.ps1 | 8 +- .../Initialize-InstallCategoryAppList.ps1 | 84 ++++++++++++++----- functions/public/Invoke-WPFButton.ps1 | 2 + .../public/Invoke-WPFToggleAllCategories.ps1 | 51 +++++++++++ xaml/inputXML.xaml | 35 ++++++-- 15 files changed, 223 insertions(+), 66 deletions(-) create mode 100644 functions/public/Invoke-WPFToggleAllCategories.ps1 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8fc9d3d0..58c9a91f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,4 +19,4 @@ ## Checklist - [ ] My code adheres to the coding and style guidelines of the project. - [ ] I have commented my code, particularly in hard-to-understand areas. -- [ ] I have made corresponding changes to the documentation. \ No newline at end of file +- [ ] I have made corresponding changes to the documentation. diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 7bb81319..0c96acd4 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -18,7 +18,7 @@ jobs: script: | const prBody = context.payload.pull_request.body || ''; const labelsToAdd = []; - + if (/\[x\]\s*New feature/i.test(prBody)) { labelsToAdd.push('new feature'); } @@ -34,7 +34,7 @@ jobs: if (/\[x\]\s*UI\/UX improvement/i.test(prBody)) { labelsToAdd.push('ui update'); } - + if (labelsToAdd.length > 0) { await github.rest.issues.addLabels({ owner: context.repo.owner, @@ -42,4 +42,4 @@ jobs: issue_number: context.payload.pull_request.number, labels: labelsToAdd }); - } \ No newline at end of file + } diff --git a/config/appnavigation.json b/config/appnavigation.json index d7782162..de3b976e 100644 --- a/config/appnavigation.json +++ b/config/appnavigation.json @@ -38,25 +38,39 @@ "Order": "2", "Description": "Use Chocolatey for package management" }, + "WPFCollapseAllCategories": { + "Content": "Collapse All Categories", + "Category": "__Selection", + "Type": "Button", + "Order": "1", + "Description": "Collapse all application categories" + }, + "WPFExpandAllCategories": { + "Content": "Expand All Categories", + "Category": "__Selection", + "Type": "Button", + "Order": "2", + "Description": "Expand all application categories" + }, "WPFClearInstallSelection": { "Content": "Clear Selection", "Category": "__Selection", "Type": "Button", - "Order": "1", + "Order": "3", "Description": "Clear the selection of applications" }, "WPFGetInstalled": { "Content": "Get Installed", "Category": "__Selection", "Type": "Button", - "Order": "2", + "Order": "4", "Description": "Show installed applications" }, "WPFselectedAppsButton": { "Content": "Selected Apps: 0", "Category": "__Selection", "Type": "Button", - "Order": "3", + "Order": "5", "Description": "Show the selected applications" } } diff --git a/config/themes.json b/config/themes.json index b44ae419..3797e5db 100644 --- a/config/themes.json +++ b/config/themes.json @@ -1,8 +1,8 @@ { "shared":{ - "AppEntryWidth": "130", + "AppEntryWidth": "200", "AppEntryFontSize": "11", - "AppEntryMargin": "1,1,1,1", + "AppEntryMargin": "1,0,1,0", "AppEntryBorderThickness": "0", "CustomDialogFontSize": "12", "CustomDialogFontSizeHeader": "14", @@ -91,7 +91,7 @@ "AppInstallOverlayBackgroundColor":"#2E3135", "ComboBoxForegroundColor": "#F7F7F7", "ComboBoxBackgroundColor": "#1E3747", - "LabelboxForegroundColor": "#0567ff", + "LabelboxForegroundColor": "#5bdcff", "MainForegroundColor": "#F7F7F7", "MainBackgroundColor": "#232629", "LabelBackgroundColor": "#232629", diff --git a/docs/content/dev/architecture.md b/docs/content/dev/architecture.md index 5c38facb..c5e84781 100644 --- a/docs/content/dev/architecture.md +++ b/docs/content/dev/architecture.md @@ -546,5 +546,5 @@ Outputs `winutil.ps1` in the root directory. --- -**Last Updated**: January 2026 -**Maintainers**: Chris Titus Tech and contributors \ No newline at end of file +**Last Updated**: January 2026 +**Maintainers**: Chris Titus Tech and contributors diff --git a/docs/content/dev/tweaks/Essential-Tweaks/Hibernation.md b/docs/content/dev/tweaks/Essential-Tweaks/Hibernation.md index d81776ba..399304c2 100644 --- a/docs/content/dev/tweaks/Essential-Tweaks/Hibernation.md +++ b/docs/content/dev/tweaks/Essential-Tweaks/Hibernation.md @@ -30,7 +30,7 @@ "powercfg.exe /hibernate on" ], ``` - + ## Registry Changes Applications and System Components store and retrieve configuration data to modify windows settings, so we can use the registry to change many settings in one place. diff --git a/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/RemoveOneDrive.md b/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/RemoveOneDrive.md index 89b160f7..457ad452 100644 --- a/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/RemoveOneDrive.md +++ b/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/RemoveOneDrive.md @@ -13,7 +13,7 @@ Write-Host \"Uninstalling OneDrive...\" Start-Process 'C:\\Windows\\System32\\OneDriveSetup.exe' -ArgumentList '/uninstall' -Wait - + # Some of OneDrive files use explorer, and OneDrive uses FileCoAuth Write-Host \"Removing leftover OneDrive Files...\" Stop-Process -Name FileCoAuth,Explorer diff --git a/docs/content/faq.md b/docs/content/faq.md index c5f5fe13..26431ecd 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -297,5 +297,5 @@ Can't find your answer? Try these resources: --- -**Last Updated**: January 2026 +**Last Updated**: January 2026 **Found this helpful?** Consider starring the project on [GitHub](https://github.com/ChrisTitusTech/winutil)! diff --git a/docs/content/userguide/_index.md b/docs/content/userguide/_index.md index f50ccad3..74afb89e 100644 --- a/docs/content/userguide/_index.md +++ b/docs/content/userguide/_index.md @@ -54,7 +54,7 @@ Apply optimizations for performance, privacy, and usability. Choose from preset Quick fixes for common Windows problems: - Reset network settings -- Fix Windows Update issues +- Fix Windows Update issues - Repair system files - Access legacy Windows panels @@ -152,7 +152,7 @@ This User Guide covers everything you need to know: 1. **[Getting Started](getting-started/)** - Installation, first run, basic usage 2. **[Application Store](store/)** - Installing software, using presets -3. **[Tweaks](tweaks/)** - System optimizations and customizations +3. **[Tweaks](tweaks/)** - System optimizations and customizations 4. **[Features & Fixes](features/)** - Troubleshooting tools and utilities 5. **[MicroWin](microwin/)** - Creating custom Windows ISOs 6. **[Updates](updates/)** - Managing Windows Update behavior diff --git a/functions/private/Find-AppsByNameOrDescription.ps1 b/functions/private/Find-AppsByNameOrDescription.ps1 index 3300d88b..4bf55cb7 100644 --- a/functions/private/Find-AppsByNameOrDescription.ps1 +++ b/functions/private/Find-AppsByNameOrDescription.ps1 @@ -13,37 +13,68 @@ function Find-AppsByNameOrDescription { # Reset the visibility if the search string is empty or the search is cleared if ([string]::IsNullOrWhiteSpace($SearchString)) { $sync.ItemsControl.Items | ForEach-Object { + # Each item is a StackPanel container $_.Visibility = [Windows.Visibility]::Visible - $_.Children | ForEach-Object { - if ($null -ne $_) { - $_.Visibility = [Windows.Visibility]::Visible + + if ($_.Children.Count -ge 2) { + $categoryLabel = $_.Children[0] + $wrapPanel = $_.Children[1] + + # Keep category label visible + $categoryLabel.Visibility = [Windows.Visibility]::Visible + + # Respect the collapsed state of categories (indicated by + prefix) + if ($categoryLabel.Content -like "+*") { + $wrapPanel.Visibility = [Windows.Visibility]::Collapsed + } else { + $wrapPanel.Visibility = [Windows.Visibility]::Visible } + # Show all apps within the category + $wrapPanel.Children | ForEach-Object { + $_.Visibility = [Windows.Visibility]::Visible + } } } return } + + # Perform search $sync.ItemsControl.Items | ForEach-Object { - # Ensure ToggleButtons remain visible - if ($_.Tag -like "CategoryToggleButton") { - $_.Visibility = [Windows.Visibility]::Visible - return - } - # Hide all CategoryWrapPanel and ToggleButton - $_.Visibility = [Windows.Visibility]::Collapsed - if ($_.Tag -like "CategoryWrapPanel_*") { - # Search for Apps that match the search string - $_.Children | Foreach-Object { + # Each item is a StackPanel container with Children[0] = label, Children[1] = WrapPanel + if ($_.Children.Count -ge 2) { + $categoryLabel = $_.Children[0] + $wrapPanel = $_.Children[1] + $categoryHasMatch = $false + + # Keep category label visible + $categoryLabel.Visibility = [Windows.Visibility]::Visible + + # Search through apps in this category + $wrapPanel.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) { + $wrapPanel.Visibility = [Windows.Visibility]::Visible + $_.Visibility = [Windows.Visibility]::Visible + # Update category label to show expanded state (-) + if ($categoryLabel.Content -like "+*") { + $categoryLabel.Content = $categoryLabel.Content -replace "^\+ ", "- " + } + } else { + # Hide the entire category container if no matches + $_.Visibility = [Windows.Visibility]::Collapsed + } } } } 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 6daded27..ce78e193 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,71 @@ 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.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "LabelboxForegroundColor") + $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 = "Horizontal" - $wrapPanel.HorizontalAlignment = "Stretch" - $wrapPanel.VerticalAlignment = "Center" - $wrapPanel.Margin = New-Object Windows.Thickness(0, 0, 0, 20) + $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/functions/public/Invoke-WPFButton.ps1 b/functions/public/Invoke-WPFButton.ps1 index 68f91e2e..1743a671 100644 --- a/functions/public/Invoke-WPFButton.ps1 +++ b/functions/public/Invoke-WPFButton.ps1 @@ -23,6 +23,8 @@ function Invoke-WPFButton { "WPFInstall" {Invoke-WPFInstall} "WPFUninstall" {Invoke-WPFUnInstall} "WPFInstallUpgrade" {Invoke-WPFInstallUpgrade} + "WPFCollapseAllCategories" {Invoke-WPFToggleAllCategories -Action "Collapse"} + "WPFExpandAllCategories" {Invoke-WPFToggleAllCategories -Action "Expand"} "WPFStandard" {Invoke-WPFPresets "Standard" -checkboxfilterpattern "WPFTweak*"} "WPFMinimal" {Invoke-WPFPresets "Minimal" -checkboxfilterpattern "WPFTweak*"} "WPFClearTweaksSelection" {Invoke-WPFPresets -imported $true -checkboxfilterpattern "WPFTweak*"} diff --git a/functions/public/Invoke-WPFToggleAllCategories.ps1 b/functions/public/Invoke-WPFToggleAllCategories.ps1 new file mode 100644 index 00000000..960a265d --- /dev/null +++ b/functions/public/Invoke-WPFToggleAllCategories.ps1 @@ -0,0 +1,51 @@ +function Invoke-WPFToggleAllCategories { + <# + .SYNOPSIS + Expands or collapses all categories in the Install tab + + .PARAMETER Action + The action to perform: "Expand" or "Collapse" + + .DESCRIPTION + This function iterates through all category containers in the Install tab + and expands or collapses their WrapPanels while updating the toggle button labels + #> + + param( + [Parameter(Mandatory=$true)] + [ValidateSet("Expand", "Collapse")] + [string]$Action + ) + + try { + if ($null -eq $sync.ItemsControl) { + Write-Warning "ItemsControl not initialized" + return + } + + $targetVisibility = if ($Action -eq "Expand") { [Windows.Visibility]::Visible } else { [Windows.Visibility]::Collapsed } + $targetPrefix = if ($Action -eq "Expand") { "-" } else { "+" } + $sourcePrefix = if ($Action -eq "Expand") { "+" } else { "-" } + + # Iterate through all items in the ItemsControl + $sync.ItemsControl.Items | ForEach-Object { + $categoryContainer = $_ + + # Check if this is a category container (StackPanel with children) + if ($categoryContainer -is [System.Windows.Controls.StackPanel] -and $categoryContainer.Children.Count -ge 2) { + # Get the WrapPanel (second child) + $wrapPanel = $categoryContainer.Children[1] + $wrapPanel.Visibility = $targetVisibility + + # Update the label to show the correct state + $categoryLabel = $categoryContainer.Children[0] + if ($categoryLabel.Content -like "$sourcePrefix*") { + $categoryLabel.Content = $categoryLabel.Content -replace "^$sourcePrefix ", "$targetPrefix " + } + } + } + } + catch { + Write-Error "Error toggling categories: $_" + } +} diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index c241d399..b9db5655 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -23,7 +23,7 @@ - + @@ -85,7 +85,7 @@