lib/ui.ps1
|
# WinMole Interactive UI Components # PwshSpectreConsole-based menus and prompts # Requires: lib/core.ps1 already dot-sourced # ── Hide/restore cursor (VT100 sequences) ──────────────────────────────────── $script:_ESC = [char]27 function Hide-Cursor { Write-Host "$($script:_ESC)[?25l" -NoNewline } function Show-Cursor { Write-Host "$($script:_ESC)[?25h" -NoNewline } # ── Read a single keypress (arrow-key aware) ────────────────────────────────── # Returns: 'Up','Down','Left','Right','Enter','Space','Delete','Backspace', # 'Home','End','PageUp','PageDown','Escape','Tab', or the char itself. function Read-Key { $k = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') switch ($k.VirtualKeyCode) { 38 { return 'Up' } 40 { return 'Down' } 37 { return 'Left' } 39 { return 'Right' } 13 { return 'Enter' } 32 { return 'Space' } 46 { return 'Delete' } 8 { return 'Backspace' } 36 { return 'Home' } 35 { return 'End' } 33 { return 'PageUp' } 34 { return 'PageDown' } 27 { return 'Escape' } 9 { return 'Tab' } default { $c = $k.Character switch ($c) { 'j' { return 'Down' } 'k' { return 'Up' } 'h' { return 'Left' } 'l' { return 'Right' } 'q' { return 'Escape' } 'Q' { return 'Escape' } default { return [string]$c } } } } } # ── Multi-select checklist menu ─────────────────────────────────────────────── # Items: array of pscustomobject with .Label, .Size (optional), .Tag (optional), # .Selected (bool), .Disabled (bool, optional) # Returns: the items array with updated .Selected values, or $null if cancelled. function Show-ChecklistMenu { param( [string]$Title, [object[]]$Items, [int]$MaxVisible = 18 ) if ($Items.Count -eq 0) { return $Items } $labels = [System.Collections.Generic.List[string]]::new() $labelToItem = @{} foreach ($item in $Items) { $label = Esc $item.Label if ($item.Size) { $label += " $(Format-Bytes $item.Size)" } if ($item.Tag) { $label += " | $($item.Tag)" } $labels.Add($label) $labelToItem[$label] = $item } try { $selected = $labels | Read-SpectreMultiSelection -Title $Title -PageSize $MaxVisible } catch { return $null } if (-not $selected) { return $null } foreach ($item in $Items) { $item.Selected = $false } $selectedSet = [System.Collections.Generic.HashSet[string]]::new([string[]]@($selected)) foreach ($label in $labels) { if ($selectedSet.Contains($label) -and $labelToItem.ContainsKey($label)) { $labelToItem[$label].Selected = $true } } return $Items } # ── Simple single-select menu ───────────────────────────────────────────────── function Show-SelectMenu { param( [string]$Title, [string[]]$Options, [int]$MaxVisible = 20 ) try { $selected = $Options | Read-SpectreSelection -Title $Title -PageSize $MaxVisible } catch { return -1 } if (-not $selected) { return -1 } return [Array]::IndexOf($Options, $selected) } |