Private/MenuArrowMulti.ps1
|
function Show-InTUIMenuArrowMulti { <# .SYNOPSIS Arrow-key multi-selection menu. Space toggles, A toggles all, Enter confirms. Returns array of selected indices. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string[]]$Choices, [Parameter()] [switch]$IncludeBack, [Parameter()] [int]$PageSize = 15 ) if ($Choices.Count -eq 0) { return @() } $palette = Get-InTUIColorPalette $reset = $palette.Reset $innerWidth = Get-InTUIConsoleInnerWidth $border = [char]0x2502 $hLine = [char]0x2500 # Render top border with gradient (once, before the loop) $topGradient = Get-InTUIGradientLine -Character $hLine -Width $innerWidth Write-Host (' {0}{1}{2}{3}{4}' -f $palette.SurfaceFg, ([char]0x256D), $topGradient, ([char]0x256E), $reset) # Render title inside the box $plainTitle = Strip-InTUIMarkup -Text $Title $titlePadded = (' {0}' -f $plainTitle).PadRight($innerWidth) Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.SurfaceFg, $border, $palette.Teal, $palette.Bold, $titlePadded, $reset, ('{0}{1}{2}' -f $palette.SurfaceFg, $border, $reset)) # Title underline $titleUnderline = (' {0}' -f ([string]([char]0x2500) * $plainTitle.Length)).PadRight($innerWidth) Write-Host (' {0}{1}{2}{3}{4}{5}' -f $palette.SurfaceFg, $border, $palette.Dim, $titleUnderline, $reset, ('{0}{1}{2}' -f $palette.SurfaceFg, $border, $reset)) # Record anchor position $anchorTop = [Console]::CursorTop $selectedIndex = 0 $itemCount = $Choices.Count $checked = [bool[]]::new($itemCount) # Viewport sizing (chrome = empty + empty + separator + hint + bottom-border + cursor-below) $chromeRows = 6 # Ensure buffer has room for items + chrome before computing viewport $anchorTop = Ensure-InTUIBufferSpace -AnchorTop $anchorTop -NeededRows ([math]::Min($itemCount, $PageSize) + $chromeRows) $maxVisible = [Math]::Max(3, [Console]::WindowHeight - $anchorTop - $chromeRows) $viewportSize = if ($itemCount -gt $maxVisible) { $maxVisible } else { 0 } $viewportOffset = 0 $adjustViewport = { if ($viewportSize -le 0) { return } $hasAbove = ($viewportOffset -gt 0) $hasBelow = (($viewportOffset + $viewportSize) -lt $itemCount) $visibleFirst = $viewportOffset + $(if ($hasAbove) { 1 } else { 0 }) $visibleLast = $viewportOffset + $viewportSize - 1 - $(if ($hasBelow) { 1 } else { 0 }) $newOffset = $viewportOffset if ($selectedIndex -lt $visibleFirst) { $newOffset = [Math]::Max(0, $selectedIndex - 1) if ($selectedIndex -eq 0) { $newOffset = 0 } } elseif ($selectedIndex -gt $visibleLast) { $newOffset = [Math]::Min($itemCount - $viewportSize, $selectedIndex - $viewportSize + 2) if ($selectedIndex -eq ($itemCount - 1)) { $newOffset = $itemCount - $viewportSize } } Set-Variable -Name viewportOffset -Value $newOffset -Scope 1 } try { try { [Console]::CursorVisible = $false } catch { $null = $_ } # Initial render & $adjustViewport Render-InTUIMenuBox -Items $Choices -SelectedIndex $selectedIndex -AnchorTop $anchorTop ` -Checked $checked -MultiSelect -IncludeBack:$IncludeBack ` -ViewportOffset $viewportOffset -ViewportSize $viewportSize while ($true) { $key = [Console]::ReadKey($true) switch ($key.Key) { 'UpArrow' { if ($selectedIndex -gt 0) { $selectedIndex-- } else { $selectedIndex = $itemCount - 1 } } 'DownArrow' { if ($selectedIndex -lt ($itemCount - 1)) { $selectedIndex++ } else { $selectedIndex = 0 } } 'Spacebar' { $checked[$selectedIndex] = -not $checked[$selectedIndex] } 'A' { $allChecked = ($checked | Where-Object { $_ }).Count -eq $itemCount for ($i = 0; $i -lt $itemCount; $i++) { $checked[$i] = -not $allChecked } } 'Enter' { $result = @() for ($i = 0; $i -lt $itemCount; $i++) { if ($checked[$i]) { $result += $i } } return $result } 'Escape' { return @() } } & $adjustViewport Render-InTUIMenuBox -Items $Choices -SelectedIndex $selectedIndex -AnchorTop $anchorTop ` -Checked $checked -MultiSelect -IncludeBack:$IncludeBack ` -ViewportOffset $viewportOffset -ViewportSize $viewportSize } } finally { try { [Console]::CursorVisible = $true } catch { $null = $_ } } } |