Files
winutil/functions/public/Invoke-WPFUIElements.ps1
4hm3dn3d41 376156f072 Rework foss filtering (#4696)
* Replace FOSS highlight toggle with inline label

Remove the separate "Highlight FOSS" toggle and its theming logic, and instead append an inline "- FOSS" label to FOSS app entries. Changes: remove WPFToggleFOSSHighlight from app navigation and related event handlers, delete FOSSColor resource updates in theme code and button handler, simplify checkbox setup in Invoke-WPFUIElements by removing the special-case toggle logic, and update Initialize-InstallAppEntry to build a horizontal panel containing the app name plus a small green "- FOSS" TextBlock for FOSS items. This consolidates FOSS indication into the item UI and cleans up toggle/theme handling.

* Add inline FOSS label to app entries

Replace the separate StackPanel/TextBlock used for the FOSS indicator with an inline System.Windows.Documents.Run appended to the app name. The run is styled (green RGB(76,175,80) and FontSize=10) and the checkbox content assignment was moved outside the conditional so the appName (with optional FOSS run) is always used. Cleaned up related comments to reflect the FOSS label change.

* Add FOSS note type with green bullet

Added FOSS notes in the UI and FOSS labels with a green bullet instead of the text '#FOSS'

* Use larger circle glyph; remove italic text

Replaced the FOSS and list bullet glyphs with a larger circle for better visual consistency and removed the italic styling

* Update FOSS/bullet glyphs, colors and sizes

Updated FOSS/list bullet styling by switching glyphs from U+2B24 to U+25CF, increasing size (10→11.5), and adjusting bullet/text colors for better visibility and consistency across.
2026-06-22 14:23:09 -05:00

407 lines
20 KiB
PowerShell

function Invoke-WPFUIElements {
<#
.SYNOPSIS
Adds UI elements to a specified Grid in the WinUtil GUI based on a JSON configuration.
.PARAMETER configVariable
The variable/link containing the JSON configuration.
.PARAMETER targetGridName
The name of the grid to which the UI elements should be added.
.PARAMETER columncount
The number of columns to be used in the Grid. If not provided, a default value is used based on the panel.
.EXAMPLE
Invoke-WPFUIElements -configVariable $sync.configs.applications -targetGridName "install" -columncount 5
.NOTES
Future me/contributor: If possible, please wrap this into a runspace to make it load all panels at the same time.
#>
param(
[Parameter(Mandatory, Position = 0)]
[PSCustomObject]$configVariable,
[Parameter(Mandatory, Position = 1)]
[string]$targetGridName,
[Parameter(Mandatory, Position = 2)]
[int]$columncount
)
$window = $sync.form
$borderstyle = $window.FindResource("BorderStyle")
$HoverTextBlockStyle = $window.FindResource("HoverTextBlockStyle")
$ColorfulToggleSwitchStyle = $window.FindResource("ColorfulToggleSwitchStyle")
$ToggleButtonStyle = $window.FindResource("ToggleButtonStyle")
if (!$borderstyle -or !$HoverTextBlockStyle -or !$ColorfulToggleSwitchStyle) {
throw "Failed to retrieve Styles using 'FindResource' from main window element."
}
$targetGrid = $window.FindName($targetGridName)
if (!$targetGrid) {
throw "Failed to retrieve Target Grid by name, provided name: $targetGrid"
}
# Clear existing ColumnDefinitions and Children
$targetGrid.ColumnDefinitions.Clear() | Out-Null
$targetGrid.Children.Clear() | Out-Null
# Add ColumnDefinitions to the target Grid
for ($i = 0; $i -lt $columncount; $i++) {
$colDef = New-Object Windows.Controls.ColumnDefinition
$colDef.Width = New-Object Windows.GridLength(1, [Windows.GridUnitType]::Star)
$targetGrid.ColumnDefinitions.Add($colDef) | Out-Null
}
# Convert PSCustomObject to Hashtable
$configHashtable = @{}
$configVariable.PSObject.Properties.Name | ForEach-Object {
$configHashtable[$_] = $configVariable.$_
}
$radioButtonGroups = @{}
$organizedData = @{}
# Iterate through JSON data and organize by panel and category
foreach ($entry in $configHashtable.Keys) {
$entryInfo = $configHashtable[$entry]
# Create an object for the application
$entryObject = [PSCustomObject]@{
Name = $entry
Category = $entryInfo.Category
Content = $entryInfo.Content
Panel = if ($entryInfo.Panel) { $entryInfo.Panel } else { "0" }
Link = $entryInfo.link
Description = $entryInfo.description
Type = $entryInfo.type
ComboItems = $entryInfo.ComboItems
Checked = $entryInfo.Checked
ButtonWidth = $entryInfo.ButtonWidth
GroupName = $entryInfo.GroupName # Added for RadioButton groupings
}
if (-not $organizedData.ContainsKey($entryObject.Panel)) {
$organizedData[$entryObject.Panel] = @{}
}
if (-not $organizedData[$entryObject.Panel].ContainsKey($entryObject.Category)) {
$organizedData[$entryObject.Panel][$entryObject.Category] = @()
}
# Store application data in an array under the category
$organizedData[$entryObject.Panel][$entryObject.Category] += $entryObject
}
# Initialize panel count
$panelcount = 0
# Iterate through 'organizedData' by panel, category, and application
$count = 0
foreach ($panelKey in ($organizedData.Keys | Sort-Object)) {
# Create a Border for each column
$border = New-Object Windows.Controls.Border
$border.VerticalAlignment = "Stretch"
[System.Windows.Controls.Grid]::SetColumn($border, $panelcount)
$border.style = $borderstyle
$targetGrid.Children.Add($border) | Out-Null
# Use a DockPanel to contain the content
$dockPanelContainer = New-Object Windows.Controls.DockPanel
$border.Child = $dockPanelContainer
# Create an ItemsControl for application content
$itemsControl = New-Object Windows.Controls.ItemsControl
$itemsControl.HorizontalAlignment = 'Stretch'
$itemsControl.VerticalAlignment = 'Stretch'
# Set the ItemsPanel to a VirtualizingStackPanel
$itemsPanelTemplate = New-Object Windows.Controls.ItemsPanelTemplate
$factory = New-Object Windows.FrameworkElementFactory ([Windows.Controls.VirtualizingStackPanel])
$itemsPanelTemplate.VisualTree = $factory
$itemsControl.ItemsPanel = $itemsPanelTemplate
# Set virtualization properties
$itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::IsVirtualizingProperty, $true)
$itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::VirtualizationModeProperty, [Windows.Controls.VirtualizationMode]::Recycling)
# Add the ItemsControl directly to the DockPanel
[Windows.Controls.DockPanel]::SetDock($itemsControl, [Windows.Controls.Dock]::Bottom)
$dockPanelContainer.Children.Add($itemsControl) | Out-Null
$panelcount++
# Now proceed with adding category labels and entries to $itemsControl
foreach ($category in ($organizedData[$panelKey].Keys | Sort-Object)) {
$count++
$label = New-Object Windows.Controls.Label
$label.Content = $category -replace ".*__", ""
$label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "HeaderFontSize")
$label.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily")
$label.UseLayoutRounding = $true
$itemsControl.Items.Add($label) | Out-Null
$sync[$category] = $label
# Sort entries by type (checkboxes first, then buttons, then comboboxes) and then alphabetically by Content
$entries = $organizedData[$panelKey][$category] | Sort-Object @{Expression = {
switch ($_.Type) {
'Button' { 1 }
'Combobox' { 2 }
default { 0 }
}
}}, Content
foreach ($entryInfo in $entries) {
$count++
# Create the UI elements based on the entry type
switch ($entryInfo.Type) {
"Toggle" {
$dockPanel = New-Object Windows.Controls.DockPanel
[System.Windows.Automation.AutomationProperties]::SetName($dockPanel, $entryInfo.Content)
$checkBox = New-Object Windows.Controls.CheckBox
$checkBox.Name = $entryInfo.Name
$checkBox.HorizontalAlignment = "Right"
$checkBox.UseLayoutRounding = $true
[System.Windows.Automation.AutomationProperties]::SetName($checkBox, $entryInfo.Content)
$dockPanel.Children.Add($checkBox) | Out-Null
$checkBox.Style = $ColorfulToggleSwitchStyle
$label = New-Object Windows.Controls.Label
$label.Content = $entryInfo.Content
$label.ToolTip = $entryInfo.Description
$label.HorizontalAlignment = "Left"
$label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
$label.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor")
$label.UseLayoutRounding = $true
$dockPanel.Children.Add($label) | Out-Null
$itemsControl.Items.Add($dockPanel) | Out-Null
$sync[$entryInfo.Name] = $checkBox
$sync[$entryInfo.Name].IsChecked = (Get-WinUtilToggleStatus $entryInfo.Name)
$sync[$entryInfo.Name].Add_Checked({
[System.Object]$Sender = $args[0]
Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $Sender.name
# Skip applying tweaks while an import is restoring toggle states
if (-not $sync.ImportInProgress) {
Invoke-WinUtilTweaks $Sender.name
}
})
$sync[$entryInfo.Name].Add_Unchecked({
[System.Object]$Sender = $args[0]
Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $Sender.name
# Skip undoing tweaks while an import is restoring toggle states
if (-not $sync.ImportInProgress) {
Invoke-WinUtiltweaks $Sender.name -undo $true
}
})
}
"ToggleButton" {
$toggleButton = New-Object Windows.Controls.Primitives.ToggleButton
$toggleButton.Name = $entryInfo.Name
$toggleButton.Content = $entryInfo.Content[1]
$toggleButton.ToolTip = $entryInfo.Description
$toggleButton.HorizontalAlignment = "Left"
$toggleButton.Style = $ToggleButtonStyle
[System.Windows.Automation.AutomationProperties]::SetName($toggleButton, $entryInfo.Content[0])
$toggleButton.Tag = @{
contentOn = if ($entryInfo.Content.Count -ge 1) { $entryInfo.Content[0] } else { "" }
contentOff = if ($entryInfo.Content.Count -ge 2) { $entryInfo.Content[1] } else { $contentOn }
}
$itemsControl.Items.Add($toggleButton) | Out-Null
$sync[$entryInfo.Name] = $toggleButton
$sync[$entryInfo.Name].Add_Checked({
$this.Content = $this.Tag.contentOn
})
$sync[$entryInfo.Name].Add_Unchecked({
$this.Content = $this.Tag.contentOff
})
}
"Combobox" {
$horizontalStackPanel = New-Object Windows.Controls.StackPanel
$horizontalStackPanel.Orientation = "Horizontal"
$horizontalStackPanel.Margin = "0,5,0,0"
[System.Windows.Automation.AutomationProperties]::SetName($horizontalStackPanel, $entryInfo.Content)
$label = New-Object Windows.Controls.Label
$label.Content = $entryInfo.Content
$label.HorizontalAlignment = "Left"
$label.VerticalAlignment = "Center"
$label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
$label.UseLayoutRounding = $true
$horizontalStackPanel.Children.Add($label) | Out-Null
$comboBox = New-Object Windows.Controls.ComboBox
$comboBox.Name = $entryInfo.Name
$comboBox.SetResourceReference([Windows.Controls.Control]::HeightProperty, "ButtonHeight")
$comboBox.SetResourceReference([Windows.Controls.Control]::WidthProperty, "ButtonWidth")
$comboBox.HorizontalAlignment = "Left"
$comboBox.VerticalAlignment = "Center"
$comboBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin")
$comboBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
$comboBox.UseLayoutRounding = $true
[System.Windows.Automation.AutomationProperties]::SetName($comboBox, $entryInfo.Content)
foreach ($comboitem in ($entryInfo.ComboItems -split " ")) {
$comboBoxItem = New-Object Windows.Controls.ComboBoxItem
$comboBoxItem.Content = $comboitem
$comboBoxItem.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
$comboBoxItem.UseLayoutRounding = $true
$comboBox.Items.Add($comboBoxItem) | Out-Null
}
$horizontalStackPanel.Children.Add($comboBox) | Out-Null
$itemsControl.Items.Add($horizontalStackPanel) | Out-Null
$comboBox.SelectedIndex = 0
# Set initial text
if ($comboBox.Items.Count -gt 0) {
$comboBox.Text = $comboBox.Items[0].Content
}
# Add SelectionChanged event handler to update the text property
$comboBox.Add_SelectionChanged({
$selectedItem = $this.SelectedItem
if ($selectedItem) {
$this.Text = $selectedItem.Content
}
})
$sync[$entryInfo.Name] = $comboBox
}
"Button" {
$button = New-Object Windows.Controls.Button
$button.Name = $entryInfo.Name
$button.Content = $entryInfo.Content
$button.HorizontalAlignment = "Left"
$button.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin")
$button.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
if ($entryInfo.ButtonWidth) {
$baseWidth = [int]$entryInfo.ButtonWidth
$button.Width = [math]::Max($baseWidth, 350)
}
[System.Windows.Automation.AutomationProperties]::SetName($button, $entryInfo.Content)
$itemsControl.Items.Add($button) | Out-Null
$sync[$entryInfo.Name] = $button
}
"RadioButton" {
# Check if a container for this GroupName already exists
if (-not $radioButtonGroups.ContainsKey($entryInfo.GroupName)) {
# Create a StackPanel for this group
$groupStackPanel = New-Object Windows.Controls.StackPanel
$groupStackPanel.Orientation = "Vertical"
[System.Windows.Automation.AutomationProperties]::SetName($groupStackPanel, $entryInfo.GroupName)
# Add the group container to the ItemsControl
$itemsControl.Items.Add($groupStackPanel) | Out-Null
}
else {
# Retrieve the existing group container
$groupStackPanel = $radioButtonGroups[$entryInfo.GroupName]
}
# Create the RadioButton
$radioButton = New-Object Windows.Controls.RadioButton
$radioButton.Name = $entryInfo.Name
$radioButton.GroupName = $entryInfo.GroupName
$radioButton.Content = $entryInfo.Content
$radioButton.HorizontalAlignment = "Left"
$radioButton.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin")
$radioButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
$radioButton.ToolTip = $entryInfo.Description
$radioButton.UseLayoutRounding = $true
[System.Windows.Automation.AutomationProperties]::SetName($radioButton, $entryInfo.Content)
if ($entryInfo.Checked -eq $true) {
$radioButton.IsChecked = $true
}
# Add the RadioButton to the group container
$groupStackPanel.Children.Add($radioButton) | Out-Null
$sync[$entryInfo.Name] = $radioButton
}
"Note" {
$textBlock = New-Object Windows.Controls.TextBlock
$textBlock.TextWrapping = "Wrap"
$textBlock.Margin = "5,5,5,5"
$textBlock.UseLayoutRounding = $true
$bulletRun = New-Object Windows.Documents.Run
$bulletRun.Text = [char]0x25CF
$bulletRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(110, 255, 114))
$bulletRun.FontSize = 11.5
$textRun = New-Object Windows.Documents.Run
$textRun.Text = " $($entryInfo.Content)"
$textRun.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
$textRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(19, 143, 83))
$textBlock.Inlines.Add($bulletRun)
$textBlock.Inlines.Add($textRun)
$itemsControl.Items.Add($textBlock) | Out-Null
}
default {
$horizontalStackPanel = New-Object Windows.Controls.StackPanel
$horizontalStackPanel.Orientation = "Horizontal"
[System.Windows.Automation.AutomationProperties]::SetName($horizontalStackPanel, $entryInfo.Content)
$checkBox = New-Object Windows.Controls.CheckBox
$checkBox.Name = $entryInfo.Name
$checkBox.Content = $entryInfo.Content
$checkBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
$checkBox.ToolTip = $entryInfo.Description
$checkBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin")
$checkBox.UseLayoutRounding = $true
[System.Windows.Automation.AutomationProperties]::SetName($checkBox, $entryInfo.Content)
if ($entryInfo.Checked -eq $true) {
$checkBox.IsChecked = $entryInfo.Checked
}
$horizontalStackPanel.Children.Add($checkBox) | Out-Null
if ($entryInfo.Link) {
$textBlock = New-Object Windows.Controls.TextBlock
$textBlock.Name = $checkBox.Name + "Link"
$textBlock.Text = "(?)"
$textBlock.ToolTip = $entryInfo.Link
$textBlock.Style = $HoverTextBlockStyle
$textBlock.UseLayoutRounding = $true
$horizontalStackPanel.Children.Add($textBlock) | Out-Null
$sync[$textBlock.Name] = $textBlock
}
$itemsControl.Items.Add($horizontalStackPanel) | Out-Null
$sync[$entryInfo.Name] = $checkBox
$sync[$entryInfo.Name].Add_Checked({
[System.Object]$Sender = $args[0]
Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $Sender.name
})
$sync[$entryInfo.Name].Add_Unchecked({
[System.Object]$Sender = $args[0]
Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkbox $Sender.name
})
}
}
}
}
}
}