From 8d3adb599f589d9c5a80f9f3fc1886fa070b28ca Mon Sep 17 00:00:00 2001 From: MyDrift Date: Thu, 6 Aug 2026 19:51:58 +0200 Subject: [PATCH] Replace the FOSS dot with a corner badge on app entries (#4924) * Replace FOSS dot with a corner badge on app entries - Add New-WinUtilFossBadge, the open source keyhole on a green backdrop - Mark FOSS apps with a corner triangle instead of a dot after the name - Give the FOSS legend a circle badge and move it below the buttons - Sort Note entries last, they shared a rank with checkboxes before Co-Authored-By: Claude Opus 5 (1M context) * Update functions/private/Initialize-InstallAppEntry.ps1 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../private/Initialize-InstallAppEntry.ps1 | 38 +++++++------ functions/private/New-WinUtilFossBadge.ps1 | 53 +++++++++++++++++++ functions/public/Invoke-WPFUIElements.ps1 | 11 ++-- 3 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 functions/private/New-WinUtilFossBadge.ps1 diff --git a/functions/private/Initialize-InstallAppEntry.ps1 b/functions/private/Initialize-InstallAppEntry.ps1 index 7fa50103..8318f12b 100644 --- a/functions/private/Initialize-InstallAppEntry.ps1 +++ b/functions/private/Initialize-InstallAppEntry.ps1 @@ -21,8 +21,9 @@ function Initialize-InstallAppEntry { $border.Tag = $appKey $border.ToolTip = $app.description $border.Add_MouseLeftButtonUp({ - $childCheckbox = ($this.Child | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0] - $childCheckBox.isChecked = -not $childCheckbox.IsChecked + # Resolve through $sync because the border's child is a layout Grid for FOSS entries + $childCheckbox = $sync.$($this.Tag) + $childCheckbox.IsChecked = -not $childCheckbox.IsChecked }) $border.Add_MouseEnter({ if (($sync.$($this.Tag).IsChecked) -eq $false) { @@ -48,15 +49,16 @@ function Initialize-InstallAppEntry { # Store the original appKey in Tag $checkBox.Tag = $appKey $checkbox.Style = $sync.Form.Resources.AppEntryCheckboxStyle + # The checkbox sits inside the entry layout Grid, so the border is one level further up $checkbox.Add_Checked({ - Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $this.Parent.Tag - $borderElement = $this.Parent + Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $this.Tag + $borderElement = $this.Parent.Parent $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallSelectedColor") }) $checkbox.Add_Unchecked({ - Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $this.Parent.Tag - $borderElement = $this.Parent + Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $this.Tag + $borderElement = $this.Parent.Parent $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") }) @@ -88,15 +90,6 @@ function Initialize-InstallAppEntry { $appName = New-Object Windows.Controls.TextBlock $appName.Style = $sync.Form.Resources.AppEntryNameStyle $appName.Text = $app.content - - # Add FOSS label after the name if FOSS - if ($app.foss -eq $true) { - $fossRun = [System.Windows.Documents.Run]::new(" $([char]0x25CF)") - $fossRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(110, 255, 114)) - $fossRun.FontSize = 11.5 - - [void]$appName.Inlines.Add($fossRun) - } [void]$contentPanel.Children.Add($appName) $checkBox.Content = $contentPanel @@ -104,7 +97,20 @@ function Initialize-InstallAppEntry { $checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content) $border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content) - $border.Child = $checkBox + # Keep the same layout for every entry so the checkbox handlers can reach the border + $entryLayout = New-Object Windows.Controls.Grid + [void]$entryLayout.Children.Add($checkBox) + + # Mark FOSS apps with a corner badge, bled into the border padding so it sits on the edge + if ($app.foss -eq $true) { + $fossBadge = New-WinUtilFossBadge + $fossBadge.HorizontalAlignment = "Right" + $fossBadge.VerticalAlignment = "Top" + $fossBadge.Margin = New-Object Windows.Thickness(0, -4, -6, 0) + + [void]$entryLayout.Children.Add($fossBadge) + } + $border.Child = $entryLayout if ($sync.selectedApps -contains $appKey) { $checkBox.IsChecked = $true } diff --git a/functions/private/New-WinUtilFossBadge.ps1 b/functions/private/New-WinUtilFossBadge.ps1 new file mode 100644 index 00000000..adff2eb5 --- /dev/null +++ b/functions/private/New-WinUtilFossBadge.ps1 @@ -0,0 +1,53 @@ +function New-WinUtilFossBadge { + <# + .SYNOPSIS + Creates the FOSS marker: the open source keyhole on a green backdrop + .DESCRIPTION + Returns a fresh element on every call, because a WPF element can only have one parent. + The artwork is authored in a 22x22 box and scaled by the Viewbox, so callers only pick a size. + .PARAMETER Size + Edge length of the badge in pixels + .PARAMETER Round + Use a full circle instead of the corner triangle, for the legend rather than an app entry + #> + param( + [double]$Size = 24, + [switch]$Round + ) + + $artwork = New-Object Windows.Controls.Grid + $artwork.Width = 22 + $artwork.Height = 22 + + $backdrop = New-Object Windows.Shapes.Path + $backdrop.Fill = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(19, 143, 83)) + $keyhole = New-Object Windows.Shapes.Path + $keyhole.Stroke = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(247, 247, 247)) + + if ($Round) { + $backdrop.Data = [Windows.Media.EllipseGeometry]::new([Windows.Point]::new(11, 11), 11, 11) + # Keyhole centred in the circle, which has room for a larger ring than the triangle does + $keyhole.Data = [Windows.Media.Geometry]::Parse("M 7.673,15.751 A 5.8,5.8 0 1 1 14.327,15.751") + $keyhole.StrokeThickness = 3.4 + } else { + # Triangle filling the top right corner, its outer corner rounded to match AppEntryBorderStyle + $backdrop.Data = [Windows.Media.Geometry]::Parse("M 0,0 L 17,0 A 5,5 0 0 1 22,5 L 22,22 Z") + # Keyhole centred on the triangle's incentre (15.56, 6.44) so it keeps the same + # 1.8 clearance from all three edges + $keyhole.Data = [Windows.Media.Geometry]::Parse("M 13.61,9.225 A 3.4,3.4 0 1 1 17.51,9.225") + $keyhole.StrokeThickness = 2.4 + } + + $keyhole.StrokeStartLineCap = [Windows.Media.PenLineCap]::Round + $keyhole.StrokeEndLineCap = [Windows.Media.PenLineCap]::Round + [void]$artwork.Children.Add($backdrop) + [void]$artwork.Children.Add($keyhole) + + $badge = New-Object Windows.Controls.Viewbox + $badge.Width = $Size + $badge.Height = $Size + $badge.Child = $artwork + $badge.ToolTip = "Free and Open Source Software" + + return $badge +} diff --git a/functions/public/Invoke-WPFUIElements.ps1 b/functions/public/Invoke-WPFUIElements.ps1 index 85d0c516..83ea2730 100644 --- a/functions/public/Invoke-WPFUIElements.ps1 +++ b/functions/public/Invoke-WPFUIElements.ps1 @@ -143,11 +143,12 @@ function Invoke-WPFUIElements { $itemsControl.Items.Add($label) | Out-Null $sync[$category] = $label - # Sort entries by type (checkboxes first, then buttons, then comboboxes) and then alphabetically by Content + # Sort entries by type (checkboxes first, then buttons, then comboboxes, notes last) and then alphabetically by Content $entries = $organizedData[$panelKey][$category] | Sort-Object @{Expression = { switch ($_.Type) { 'Button' { 1 } 'Combobox' { 2 } + 'Note' { 3 } default { 0 } } }}, Content @@ -364,17 +365,15 @@ function Invoke-WPFUIElements { $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 + $bulletBadge = [Windows.Documents.InlineUIContainer]::new((New-WinUtilFossBadge -Size 18 -Round)) + $bulletBadge.BaselineAlignment = [Windows.BaselineAlignment]::Center $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($bulletBadge) $textBlock.Inlines.Add($textRun) $itemsControl.Items.Add($textBlock) | Out-Null