private/Controls/New-StatusBarMessagePopup.ps1
|
function New-StatusBarMessagePopup { <# .SYNOPSIS Creates a themed popup for displaying accumulated warning or error messages. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Warning', 'Error', 'Info', 'Verbose', 'Debug')] [string]$Severity, [Parameter(Mandatory)] [System.Windows.UIElement]$PlacementTarget, [hashtable]$BadgeInfo, [System.Collections.Generic.List[hashtable]]$MessageList, [System.Windows.Controls.Border]$Bar ) $colors = Get-ThemeColors # Header label colored by severity $headerBrushKey = switch ($Severity) { 'Warning' { 'WarningBrush' } 'Error' { 'ErrorBrush' } 'Info' { 'AccentBrush' } default { 'SecondaryTextBrush' } } $noun = if ($BadgeInfo -and $BadgeInfo.Noun) { $BadgeInfo.Noun } else { $Severity } $headerText = [System.Windows.Controls.TextBlock]@{ Text = "0 ${noun}s" FontWeight = 'SemiBold' FontSize = 13 VerticalAlignment = 'Center' Margin = [System.Windows.Thickness]::new(8, 6, 8, 6) } $headerText.SetResourceReference( [System.Windows.Controls.TextBlock]::ForegroundProperty, $headerBrushKey) # Clear button $clearButton = [System.Windows.Controls.Button]@{ Content = 'Clear' FontSize = 11 Padding = [System.Windows.Thickness]::new(8, 2, 8, 2) Margin = [System.Windows.Thickness]::new(0, 4, 6, 4) VerticalAlignment = 'Center' Cursor = [System.Windows.Input.Cursors]::Hand } try { Set-ButtonStyle -Button $clearButton } catch { Write-Debug "Popup clear button styling failed: $_" } $clearButton.Height = 22 $clearButton.FocusVisualStyle = $null # Copy button - dumps accumulated messages to the clipboard $copyButton = [System.Windows.Controls.Button]@{ Content = 'Copy' FontSize = 11 Padding = [System.Windows.Thickness]::new(8, 2, 8, 2) Margin = [System.Windows.Thickness]::new(0, 4, 2, 4) VerticalAlignment = 'Center' Cursor = [System.Windows.Input.Cursors]::Hand } try { Set-ButtonStyle -Button $copyButton } catch { Write-Debug "Popup copy button styling failed: $_" } $copyButton.Height = 22 $copyButton.FocusVisualStyle = $null # Close button (X glyph, inherits foreground from themed button style) $closeGlyph = [System.Windows.Controls.TextBlock]@{ Text = [char]0xE711 FontFamily = [PsUi.ModuleContext]::ActiveIconFontFamily FontSize = 10 VerticalAlignment = 'Center' HorizontalAlignment = 'Center' } $closeButton = [System.Windows.Controls.Button]@{ Content = $closeGlyph Width = 22 Height = 22 Padding = [System.Windows.Thickness]::new(0) Margin = [System.Windows.Thickness]::new(0, 4, 4, 4) VerticalAlignment = 'Center' Cursor = [System.Windows.Input.Cursors]::Hand } try { Set-ButtonStyle -Button $closeButton } catch { Write-Debug "Popup close button styling failed: $_" } $closeButton.FocusVisualStyle = $null # Header row: right-docked buttons in DockPanel insertion order (rightmost first) $headerRow = [System.Windows.Controls.DockPanel]::new() [System.Windows.Controls.DockPanel]::SetDock($closeButton, 'Right') [void]$headerRow.Children.Add($closeButton) [System.Windows.Controls.DockPanel]::SetDock($clearButton, 'Right') [void]$headerRow.Children.Add($clearButton) [System.Windows.Controls.DockPanel]::SetDock($copyButton, 'Right') [void]$headerRow.Children.Add($copyButton) [void]$headerRow.Children.Add($headerText) # Separator $separator = [System.Windows.Controls.Separator]@{ Margin = [System.Windows.Thickness]::new(4, 0, 4, 0) } # Message panel inside a scroll viewer $messagePanel = [System.Windows.Controls.StackPanel]@{ Margin = [System.Windows.Thickness]::new(0, 4, 0, 4) } $scrollViewer = [System.Windows.Controls.ScrollViewer]@{ VerticalScrollBarVisibility = 'Auto' Content = $messagePanel } # Main container $mainPanel = [System.Windows.Controls.DockPanel]::new() [System.Windows.Controls.DockPanel]::SetDock($headerRow, 'Top') [System.Windows.Controls.DockPanel]::SetDock($separator, 'Top') [void]$mainPanel.Children.Add($headerRow) [void]$mainPanel.Children.Add($separator) [void]$mainPanel.Children.Add($scrollViewer) # Shadow effect $shadow = [System.Windows.Media.Effects.DropShadowEffect]@{ BlurRadius = 10 ShadowDepth = 2 Opacity = 0.25 Color = [System.Windows.Media.Colors]::Black } # Outer border with themed colors $outerBorder = [System.Windows.Controls.Border]@{ MinWidth = 280 MaxWidth = 450 MaxHeight = 320 CornerRadius = [System.Windows.CornerRadius]::new(4) BorderThickness = [System.Windows.Thickness]::new(1) Padding = [System.Windows.Thickness]::new(0) Effect = $shadow Child = $mainPanel } $outerBorder.SetResourceReference( [System.Windows.Controls.Border]::BackgroundProperty, 'ControlBackgroundBrush') $outerBorder.SetResourceReference( [System.Windows.Controls.Border]::BorderBrushProperty, 'BorderBrush') # Inherited foreground for all child TextBlocks - tracks the active theme # so popup entries pick up the correct color without needing explicit brushes $outerBorder.SetResourceReference( [System.Windows.Documents.TextElement]::ForegroundProperty, 'ControlForegroundBrush') # Popup $popup = [System.Windows.Controls.Primitives.Popup]@{ PlacementTarget = $PlacementTarget Placement = 'Top' StaysOpen = $false AllowsTransparency = $true Child = $outerBorder } # Clear button resets the badge and closes the popup $capturedBadge = $BadgeInfo $capturedMsgList = $MessageList $capturedPanel = $messagePanel $capturedHeader = $headerText $capturedPopup = $popup $capturedBar = $Bar $capturedNoun = $noun $clearButton.Add_Click({ $capturedMsgList.Clear() $capturedPanel.Children.Clear() $capturedHeader.Text = "0 ${capturedNoun}s" if ($capturedBadge) { $capturedBadge.CountText.Text = '0' $capturedBadge.Badge.ToolTip = "0 ${capturedNoun}s" $capturedBadge.Badge.Opacity = 0.35 } $capturedPopup.IsOpen = $false }.GetNewClosure()) # Copy button formats messages and pushes to clipboard with visual feedback $capturedCopyBtn = $copyButton $copyButton.Add_Click({ if ($capturedMsgList.Count -eq 0) { return } $lines = [System.Text.StringBuilder]::new() foreach ($msg in $capturedMsgList) { $ts = $msg.Time.ToString('HH:mm:ss') [void]$lines.AppendLine("[$ts] $($msg.Message)") if ($msg.Detail) { [void]$lines.AppendLine(" $($msg.Detail)") } if ($msg.Code) { [void]$lines.AppendLine(" > $($msg.Code)") } if ($msg.Stack) { [void]$lines.AppendLine(" $($msg.Stack)") } if ($msg.Target) { [void]$lines.AppendLine(" Target: $($msg.Target)") } if ($msg.Inner) { [void]$lines.AppendLine(" Inner: $($msg.Inner)") } } try { [System.Windows.Clipboard]::SetText($lines.ToString().TrimEnd()) } catch { Write-Debug "Clipboard copy failed: $_"; return } # Flash "Copied!" then revert after 1.5s (fetch colors fresh for theme accuracy) $capturedCopyBtn.Content = 'Copied!' $originalBg = $capturedCopyBtn.Background $currentColors = Get-ThemeColors try { $capturedCopyBtn.Background = ConvertTo-UiBrush $currentColors.Accent } catch { Write-Debug "Copy feedback accent failed: $_" } $timer = [System.Windows.Threading.DispatcherTimer]::new() $timer.Interval = [TimeSpan]::FromMilliseconds(1500) $timer.Tag = @{ Btn = $capturedCopyBtn; Bg = $originalBg } $timer.Add_Tick({ $this.Tag.Btn.Background = $this.Tag.Bg $this.Tag.Btn.Content = 'Copy' $this.Stop() }) $timer.Start() }.GetNewClosure()) # Close button just dismisses the popup $closeButton.Add_Click({ $capturedPopup.IsOpen = $false }.GetNewClosure()) @{ Popup = $popup MessagePanel = $messagePanel HeaderText = $headerText ClearButton = $clearButton } } |