public/controls/status/New-UiProgress.ps1
|
function New-UiProgress { <# .SYNOPSIS Creates a progress bar. .DESCRIPTION Determinate or indeterminate progress bar. Drive it with Set-UiProgress while an action runs, or just assign the hydrated variable and let it sync at action end. .PARAMETER Variable Variable name to reference the bar later. .PARAMETER Label Optional text shown above the bar. Updateable via Set-UiProgress -Label. .PARAMETER Minimum Lower limit. Default 0. .PARAMETER Maximum Upper limit. Default 100. .PARAMETER Default Initial value. Default 0. .PARAMETER Height Bar height in pixels. .PARAMETER Indeterminate Animated bar instead of a fill. .PARAMETER ShowValue Render the current value as text beside the bar. Pointless with -Indeterminate (the bar has no real value to display), but allowed. .PARAMETER ValueFormat Format string for the value text. {0} is current, {1} is max. Defaults to '{0:N0}%'. '{0}/{1}' gives you the x/y look. .PARAMETER Severity Color tint: Info (default), Success, Warning, Error. .PARAMETER WPFProperties Hashtable of additional WPF properties. .EXAMPLE New-UiProgress -Variable 'progress' .EXAMPLE New-UiProgress -Variable 'loading' -Indeterminate -Label 'Connecting...' .EXAMPLE New-UiProgress -Variable 'files' -Maximum 250 -ShowValue -ValueFormat '{0}/{1} files' .EXAMPLE New-UiProgress -Variable 'disk' -Default 87 -ShowValue -Severity Warning -Label 'Disk usage' .EXAMPLE # Set-UiProgress drives the bar from any action New-UiProgress -Variable 'copyBar' -ShowValue New-UiButton -Text 'Copy' -NoOutput -Action { foreach ($step in 1..10) { Set-UiProgress -Variable 'copyBar' -Value ($step * 10) Start-Sleep -Milliseconds 250 } } #> [CmdletBinding()] param( # Optional. A display bar on its own needs no name, and leaving it out means the bar is never registered for Set-UiProgress by name. The body puts up a throwaway name for the registry. [string]$Variable, [string]$Label, [double]$Minimum = 0, [double]$Maximum = 100, [double]$Default = 0, [int]$Height = 6, [switch]$Indeterminate, [switch]$ShowValue, [string]$ValueFormat = '{0:N0}%', [ValidateSet('Info', 'Success', 'Warning', 'Error')] [string]$Severity = 'Info', [Parameter()] [hashtable]$WPFProperties ) $session = Assert-UiSession -CallerName 'New-UiProgress' $parent = $session.CurrentParent Write-Debug "Variable='$Variable', Min=$Minimum Max=$Maximum Default=$Default Severity=$Severity" if ($Maximum -le $Minimum) { throw "New-UiProgress: -Maximum ($Maximum) must be greater than -Minimum ($Minimum)." } $progress = [System.Windows.Controls.ProgressBar]::new() $progress.Minimum = $Minimum $progress.Maximum = $Maximum $progress.Value = [Math]::Max($Minimum, [Math]::Min($Maximum, $Default)) $progress.IsIndeterminate = $Indeterminate.IsPresent $progress.Margin = [System.Windows.Thickness]::new(4, 4, 4, 8) # ThemeEngine reads Tag.BrushTag and rebinds Foreground on theme switches. $brushKey = Get-SeverityBrushKey -Severity $Severity -UseAccentDefault # Tag goes on early so the first ApplyTheme pass picks up the right brush. # ValueBlock/LabelBlock get filled in below if the wrapper is built. $labelBlock = $null $valueBlock = $null $progress.Tag = @{ LabelBlock = $labelBlock ValueBlock = $valueBlock ValueFormat = $ValueFormat Severity = $Severity BrushTag = $brushKey } Set-ProgressBarStyle -ProgressBar $progress if ($PSBoundParameters.ContainsKey('Height')) { $progress.Height = $Height } if ($WPFProperties) { # Tag is reserved - it stores the metadata that makes -Label/-ShowValue/-Severity work. Letting the caller stomp it would silently break all three. if ($WPFProperties.ContainsKey('Tag')) { Write-Warning "New-UiProgress: -WPFProperties Tag is reserved (used for label/value/severity bookkeeping). Ignoring." $WPFProperties = @{} + $WPFProperties [void]$WPFProperties.Remove('Tag') } Set-UiProperties -Control $progress -Properties $WPFProperties } # Wrap in a stack only when there's a label or value text. Unwrapped bars stay unwrapped so existing layouts don't shift a pixel. $needsWrapper = $Label -or $ShowValue if ($needsWrapper) { $colors = Get-ThemeColors $stack = [System.Windows.Controls.StackPanel]::new() $stack.Margin = [System.Windows.Thickness]::new(4, 4, 4, 8) if ($Label) { $labelBlock = [System.Windows.Controls.TextBlock]@{ Text = $Label FontSize = 12 Foreground = ConvertTo-UiBrush $colors.ControlFg Margin = [System.Windows.Thickness]::new(0, 0, 0, 4) Tag = 'ControlForegroundBrush' } [PsUi.ThemeEngine]::RegisterElement($labelBlock) [void]$stack.Children.Add($labelBlock) } # Bar + value text share a row so the percentage rides next to it if ($ShowValue) { $row = [System.Windows.Controls.Grid]::new() $col1 = [System.Windows.Controls.ColumnDefinition]::new() $col1.Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star) $col2 = [System.Windows.Controls.ColumnDefinition]::new() $col2.Width = [System.Windows.GridLength]::Auto $row.ColumnDefinitions.Add($col1) $row.ColumnDefinitions.Add($col2) $progress.Margin = [System.Windows.Thickness]::new(0, 0, 8, 0) $progress.VerticalAlignment = 'Center' [System.Windows.Controls.Grid]::SetColumn($progress, 0) [void]$row.Children.Add($progress) $valueBlock = [System.Windows.Controls.TextBlock]@{ Text = ($ValueFormat -f $progress.Value, $Maximum) FontSize = 12 Foreground = ConvertTo-UiBrush $colors.ControlFg VerticalAlignment = 'Center' MinWidth = 48 TextAlignment = 'Right' Tag = 'ControlForegroundBrush' } [PsUi.ThemeEngine]::RegisterElement($valueBlock) [System.Windows.Controls.Grid]::SetColumn($valueBlock, 1) [void]$row.Children.Add($valueBlock) # Update the text whenever the bar moves. Read Maximum from the sender so post-construction tweaks (yes, people do that) stay in sync. $capturedFormat = $ValueFormat $progress.Add_ValueChanged({ param($sender, $e) $valueBlock.Text = $capturedFormat -f $e.NewValue, $sender.Maximum }.GetNewClosure()) [void]$stack.Children.Add($row) } else { [void]$stack.Children.Add($progress) } [void]$parent.Children.Add($stack) } else { [void]$parent.Children.Add($progress) } # Patch the metadata with the actual wrapper blocks (if any). $progress.Tag.LabelBlock = $labelBlock $progress.Tag.ValueBlock = $valueBlock # A display bar on its own has no -Variable, but Register-UiControlComplete's -Name is Mandatory, and an empty string there makes PowerShell prompt for it and the whole window hangs. Hand it a throwaway name (same trick New-UiStatusBar uses for anonymous bars). $varName = if ($Variable) { $Variable } else { '_anonProgress_' + [System.Guid]::NewGuid().ToString('N').Substring(0, 8) } Write-Debug "Registered progress bar '$varName' (wrapper=$needsWrapper)" Register-UiControlComplete -Name $varName -Control $progress -InitialValue $progress.Value } |