public/controls/Set-UiStatusBar.ps1
|
function Set-UiStatusBar { <# .SYNOPSIS Updates status bar text, progress, and severity from any thread. .DESCRIPTION Pass any combination of parameters. Only bound parameters take effect. -Severity tint auto-resets to Info after 5 seconds unless -Timeout overrides; pass 0 to keep the tint until the next change. .PARAMETER Text Status text. Sets .Text on the first TextBlock child. Pass an empty string to clear. .PARAMETER Progress Progress value (0-100). Sets .Value on the embedded progress bar. If both -Progress and -Increment are bound, -Progress wins. A value above zero holds the bar on screen across actions; zero releases the hold and hides the bar. For a visible not-started state, use -Indeterminate instead of zero. .PARAMETER Increment Adds to the current progress value. Clamps to [0, 100]. Ignored when -Progress is also bound. .PARAMETER Severity Bar tint: Info (default), Success, Warning, Error. Survives theme switches. .PARAMETER Indeterminate Toggles indeterminate mode on the embedded progress bar. .PARAMETER Timeout Seconds before severity auto-resets to Info. Defaults to 5 when -Severity is bound. Pass 0 to keep the tint until the next manual change. .PARAMETER Variable Session name the bar was registered under. Resolves the active bar when omitted. .EXAMPLE Set-UiStatusBar -Text 'Deploying...' -Progress 87 -Severity Warning .EXAMPLE Set-UiStatusBar -Severity Error -Timeout 0 -Text 'Failed' #> [CmdletBinding()] param( [string]$Text, # Skipping [ValidateRange] here and on -Increment. Percentage math can occasionally round up to 101, and crashing the loop with a parameter error is much worse than just capping the progress bar. [int]$Progress, [int]$Increment, [ValidateSet('Info', 'Success', 'Warning', 'Error')] [string]$Severity, [switch]$Indeterminate, [int]$Timeout, [string]$Variable ) if (!$PSBoundParameters.Count) { return } # v2.x -Indeterminate took [bool]. Under [switch] a space-form '-Indeterminate $false' leaves the switch present (=$true) and spills $false to the next free positional slot: -Text when no text was passed ('False'/'True'), -Progress when text is bound (0/1), -Increment when both are (0/1). # Recover only when the switch is on - an explicit -Indeterminate:$false / splat $false is a modern caller who means it, so honor their -Text/-Progress as is. The spilled bool always lands in Text, Progress, or Increment; read it back. # Known edges, chosen not to chase: a modern caller pairing -Indeterminate with a literal -Text 'True'/'False', or with -Progress 0/1, gets reinterpreted as a v2.x spill. No in-repo caller does either, and a real status text is never the bare word 'True'. $indeterminateValue = [bool]$Indeterminate if ($Indeterminate) { if ($PSBoundParameters.ContainsKey('Text') -and $Text -in 'True', 'False') { $indeterminateValue = $Text -eq 'True' [void]$PSBoundParameters.Remove('Text') $Text = '' } elseif ($PSBoundParameters.ContainsKey('Text') -and $PSBoundParameters.ContainsKey('Progress') -and $Progress -in 0, 1 -and !$PSBoundParameters.ContainsKey('Increment')) { $indeterminateValue = $Progress -eq 1 [void]$PSBoundParameters.Remove('Progress') } # Text and Progress both bound by name pushes the spill one slot further, into -Increment. -Progress plus -Increment in one call is already contradictory (set vs bump), so a 0/1 Increment here can only be the spilled bool. elseif ($PSBoundParameters.ContainsKey('Increment') -and $Increment -in 0, 1 -and $PSBoundParameters.ContainsKey('Text') -and $PSBoundParameters.ContainsKey('Progress')) { $indeterminateValue = $Increment -eq 1 [void]$PSBoundParameters.Remove('Increment') } } $session = Get-UiSession if (!$session) { return } # Severity auto-reset: explicit -Timeout wins; default to 5s when only -Severity is bound $effectiveTimeout = 0 if ($PSBoundParameters.ContainsKey('Timeout')) { $effectiveTimeout = $Timeout } elseif ($PSBoundParameters.ContainsKey('Severity')) { $effectiveTimeout = 5 } $boundKeys = $PSBoundParameters.Keys Invoke-OnUIThread { $bar = Resolve-UiStatusBar -Variable $Variable if (!$bar) { $hint = if ($Variable) { "no control registered as '$Variable'" } else { "no status bar registered in this session" } Write-Warning "Set-UiStatusBar: $hint" return } $meta = if ($bar.Tag -is [hashtable]) { $bar.Tag } else { @{} } $textBlock = $meta.StatusText $progBar = $meta.ProgressBar # Update text and append to the bar's activity ledger if ($boundKeys -contains 'Text' -and $textBlock) { $textBlock.Text = $Text $textBlock.ToolTip = if ($Text.Length -gt 60) { $Text } else { $null } $ledgerKind = if ($boundKeys -contains 'Severity') { $Severity } else { 'Info' } try { Add-StatusBarHistoryEntry -Bar $bar -Message $Text -Kind $ledgerKind } catch { Write-Verbose "Set-UiStatusBar ledger entry failed: $_" } } # Warn when caller passed progress but the bar has no embedded progress bar $wantsProgress = ($boundKeys -contains 'Progress') -or ($boundKeys -contains 'Increment') -or ($boundKeys -contains 'Indeterminate') if ($wantsProgress -and !$progBar) { Write-Verbose "Set-UiStatusBar: -Progress/-Increment/-Indeterminate set but no embedded bar. Re-create the status bar with -AutoProgress." } if ($progBar) { # Show the bar when the caller is actively driving progress if ($wantsProgress -and $progBar.Visibility -ne [System.Windows.Visibility]::Visible) { $progBar.Visibility = [System.Windows.Visibility]::Visible } # Progress wins over Increment when both are bound if ($boundKeys -contains 'Progress') { $clamped = $Progress if ($clamped -lt 0) { $clamped = 0 } if ($clamped -gt 100) { $clamped = 100 } $progBar.Value = $clamped } elseif ($boundKeys -contains 'Increment') { $newVal = $progBar.Value + $Increment if ($newVal -lt 0) { $newVal = 0 } if ($newVal -gt 100) { $newVal = 100 } $progBar.Value = $newVal } if ($boundKeys -contains 'Indeterminate') { $progBar.IsIndeterminate = $indeterminateValue Set-ProgressBarStyle -ProgressBar $progBar } if ($wantsProgress) { if ($progBar.IsIndeterminate) { $meta.ManualBar = $true } elseif ($progBar.Value -gt 0) { if (($boundKeys -contains 'Progress') -or ($boundKeys -contains 'Increment')) { $meta.ManualBar = $true } else { $meta.ManualBar = $false } } else { $meta.ManualBar = $false $progBar.Visibility = [System.Windows.Visibility]::Hidden } } } # Apply severity tint and sync the embedded progress bar's fill if ($boundKeys -contains 'Severity') { $meta.Severity = $Severity Set-StatusBarSeverityVisual -Bar $bar -Severity $Severity if ($progBar -and $progBar.Tag -is [hashtable]) { $progBar.Tag.Severity = $Severity $progBar.Tag.BrushTag = Get-SeverityBrushKey -Severity $Severity -UseAccentDefault Set-ProgressBarStyle -ProgressBar $progBar } if ($effectiveTimeout -gt 0 -and $meta.SeverityTimer) { $meta.SeverityTimer.Stop() $meta.SeverityTimer.Interval = [TimeSpan]::FromSeconds($effectiveTimeout) $meta.SeverityTimer.Start() } elseif ($effectiveTimeout -le 0 -and $meta.SeverityTimer) { $meta.SeverityTimer.Stop() } } $bar.Tag = $meta } } |