Gumby.Window.psm1
using module Gumby.Debug using module Gumby.Log using module Gumby.Math using module Gumby.String enum WindowResult { OK Cancel } class Window { Window( [int] $left, [int] $top, [int] $width, [int] $height, [System.ConsoleColor] $foregroundColor = $Global:Host.UI.RawUI.BackgroundColor, [System.ConsoleColor] $backgroundColor = $Global:Host.UI.RawUI.ForegroundColor) { $this._rect = [System.Management.Automation.Host.Rectangle]::new( [console]::WindowLeft + $left, [console]::WindowTop + $top, [console]::WindowLeft + $left + $width - 1, [console]::WindowTop + $top + $height - 1) $this._foregroundColor = $foregroundColor $this._backgroundColor = $backgroundColor } [System.Management.Automation.Host.Coordinates] WindowCoordinates() { return [System.Management.Automation.Host.Coordinates]::new($this._rect.left, $this._rect.Top) } [System.Management.Automation.Host.Rectangle] WindowRectangle() { return $this._rect } [int] WindowWidth() { return $this._rect.Right - $this._rect.Left + 1; } [int] WindowHeight() { return $this._rect.Bottom - $this._rect.Top + 1; } [System.Management.Automation.Host.Coordinates] ClientCoordinates() { return [System.Management.Automation.Host.Coordinates]::new($this._rect.left + 1, $this._rect.Top + 1) } [System.Management.Automation.Host.Rectangle] ClientRectangle() { return [System.Management.Automation.Host.Rectangle]::new($this._rect.Left + 1, $this._rect.Top + 1, $this._rect.Right - 1, $this._rect.Bottom - 1) } [int] ClientWidth() { return $this._rect.Right - $this._rect.Left - 1; } [int] ClientHeight() { return $this._rect.Bottom - $this._rect.Top - 1; } [System.Management.Automation.Host.Coordinates] GetClientCoordinates([int] $x, [int] $y) { [int] $absX = $this._rect.Left + 1 + $x [int] $absY = $this._rect.Top + 1 + $y return [System.Management.Automation.Host.Coordinates]::new($absX, $absY) } [System.ConsoleColor] ForegroundColor() { return $this._foregroundColor } [System.ConsoleColor] BackgroundColor() { return $this._backgroundColor } [void] WriteLine([int] $lineNumber, [string] $text, $foregroundColor, $backgroundColor) { Assert ($lineNumber -lt $this.ClientHeight()) "line number outside of client area" [System.Management.Automation.Host.BufferCell[,]] $buffer = $Global:Host.UI.RawUI.NewBufferCellArray( @(EnsureStringLength $text $this.ClientWidth()), $foregroundColor, $backgroundColor) $Global:Host.UI.RawUI.SetBufferContents($this.GetClientCoordinates(0, $lineNumber), $buffer) } [void] WriteStatusBar($text) { $windowWidth = $this.WindowWidth() $t = $text.Substring(0, [math]::Min($text.Length, $windowWidth - 4)) $cell = [System.Management.Automation.Host.BufferCell]::new(' ', $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) [System.Management.Automation.Host.BufferCell[,]] $buffer = $Global:Host.UI.RawUI.NewBufferCellArray($windowWidth - 2, 1, $cell) $hBar = [System.Management.Automation.Host.BufferCell]::new([char]0x2500, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $t90 = [System.Management.Automation.Host.BufferCell]::new([char]0x2524, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $t270 = [System.Management.Automation.Host.BufferCell]::new([char]0x251C, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) [int] $a = 0 [int] $b = 0 if (IsEven $windowWidth) { if (IsEven $t.Length) { # 01234567890123456789 # └──────┤abcd├──────┘ $a = $b = ($windowWidth - 4 - $t.Length) / 2 } else { # 01234567890123456789 # └──────┤abc├───────┘ # Note that in Powershell, casting to integer performs banker's rounding. $a = [Math]::Truncate(($windowWidth - 4 - $t.Length) / 2) $b = [Math]::Truncate(($windowWidth - 3 - $t.Length) / 2) } } else { # odd window width if (IsEven $t.Length) { # 0123456789012345678 # └─────┤abcd├──────┘ $a = [Math]::Truncate(($windowWidth - 4 - $t.Length) / 2) $b = [Math]::Truncate(($windowWidth - 3 - $t.Length) / 2) } else { # 0123456789012345678 # └──────┤abc├──────┘ $a = $b = ($windowWidth - 4 - $t.Length) / 2 } } $x = 0 for ($i = 0; $i -lt $a; ++$i) { $buffer[0, $x++] = $hBar } $buffer[0, $x++]= $t90 for ($i = 0; $i -lt $t.Length; ++$i) { $buffer[0, $x++] = [System.Management.Automation.Host.BufferCell]::new($t[$i], $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) } $buffer[0, $x++]= $t270 for ($i = 0; $i -lt $b; ++$i) { $buffer[0, $x++] = $hBar } $p = [System.Management.Automation.Host.Coordinates]::new($this._rect.Left + 1, $this._rect.Bottom) $Global:Host.UI.RawUI.SetBufferContents($p, $buffer) } [void] ScrollAreaVertically([UInt32] $top, [UInt32] $bottom, [int] $amount) { [System.Management.Automation.Host.Rectangle] $source = $this.ClientRectangle() $bufferTop = $source.Top + $top $bufferBottom = $source.Top + $bottom $source.Top = $bufferTop $source.Bottom = $bufferBottom [System.Management.Automation.Host.Rectangle] $clip = $this.ClientRectangle() [System.Management.Automation.Host.Coordinates] $destination = $this.GetClientCoordinates(0, $top + $amount) [System.Management.Automation.Host.BufferCell] $fill = [System.Management.Automation.Host.BufferCell]::new(' ', $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $Global:Host.UI.RawUI.ScrollBufferContents($source, $destination, $clip, $fill) } [WindowResult] Run() { $this.SaveOriginalWindowArea() $this.DrawFrame() $this.DrawClientArea() $this.OnShown() while ($this._running) { $this.OnKey([console]::ReadKey([System.Management.Automation.Host.ReadKeyOptions]::NoEcho)) } $this.RestoreOriginalWindowArea() return $this.Result } hidden [void] DrawFrame() { $windowWidth = $this.WindowWidth() $windowHeight = $this.WindowHeight() <# 0x2500: ─ 0x2501: ━ 0x2502: │ 0x2503: ┃ 0x2504: ┄ 0x2505: ┅ 0x2506: ┆ 0x2507: ┇ 0x2508: ┈ 0x2509: ┉ 0x250A: ┊ 0x250B: ┋ 0x250C: ┌ 0x250D: ┍ 0x250E: ┎ 0x250F: ┏ 0x2510: ┐ 0x2511: ┑ 0x2512: ┒ 0x2513: ┓ 0x2514: └ 0x2515: ┕ 0x2516: ┖ 0x2517: ┗ 0x2518: ┘ 0x2519: ┙ 0x251A: ┚ 0x251B: ┛ 0x251C: ├ 0x251D: ┝ 0x251E: ┞ 0x251F: ┟ 0x2520: ┠ 0x2521: ┡ 0x2522: ┢ 0x2523: ┣ 0x2524: ┤ 0x2525: ┥ 0x2526: ┦ 0x2527: ┧ 0x2528: ┨ 0x2529: ┩ 0x252A: ┪ 0x252B: ┫ 0x252C: ┬ 0x252D: ┭ 0x252E: ┮ 0x252F: ┯ 0x2530: ┰ 0x2531: ┱ 0x2532: ┲ 0x2533: ┳ 0x2534: ┴ 0x2535: ┵ 0x2536: ┶ 0x2537: ┷ 0x2538: ┸ 0x2539: ┹ 0x253A: ┺ 0x253B: ┻ 0x253C: ┼ 0x253D: ┽ 0x253E: ┾ 0x253F: ┿ 0x2540: ╀ 0x2541: ╁ 0x2542: ╂ 0x2543: ╃ 0x2544: ╄ 0x2545: ╅ 0x2546: ╆ 0x2547: ╇ 0x2548: ╈ 0x2549: ╉ 0x254A: ╊ 0x254B: ╋ 0x254C: ╌ 0x254D: ╍ 0x254E: ╎ 0x254F: ╏ 0x2550: ═ 0x2551: ║ 0x2552: ╒ 0x2553: ╓ 0x2554: ╔ 0x2555: ╕ 0x2556: ╖ 0x2557: ╗ 0x2558: ╘ 0x2559: ╙ 0x255A: ╚ 0x255B: ╛ 0x255C: ╜ 0x255D: ╝ 0x255E: ╞ 0x255F: ╟ 0x2560: ╠ 0x2561: ╡ 0x2562: ╢ 0x2563: ╣ 0x2564: ╤ 0x2565: ╥ 0x2566: ╦ 0x2567: ╧ 0x2568: ╨ 0x2569: ╩ 0x256A: ╪ 0x256B: ╫ 0x256C: ╬ 0x256D: ╭ 0x256E: ╮ 0x256F: ╯ 0x2570: ╰ 0x2571: ╱ 0x2572: ╲ 0x2573: ╳ 0x2574: ╴ 0x2575: ╵ 0x2576: ╶ 0x2577: ╷ 0x2578: ╸ 0x2579: ╹ 0x257A: ╺ 0x257B: ╻ 0x257C: ╼ 0x257D: ╽ 0x257E: ╾ #> $cell = [System.Management.Automation.Host.BufferCell]::new(' ', $this._foregroundColor, $this._backgroundColor, ([Management.Automation.Host.BufferCellType]::Complete)) [System.Management.Automation.Host.BufferCell[,]] $buffer = $Global:Host.UI.RawUI.NewBufferCellArray($windowWidth, $windowHeight, $cell) $horizontalBar = [System.Management.Automation.Host.BufferCell]::new([char]0x2500, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $verticalBar = [System.Management.Automation.Host.BufferCell]::new([char]0x2502, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $topLeftCorner = [System.Management.Automation.Host.BufferCell]::new([char]0x250C, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $topRightCorner = [System.Management.Automation.Host.BufferCell]::new([char]0x2510, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $bottomLeftCorner = [System.Management.Automation.Host.BufferCell]::new([char]0x2514, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $bottomRightCorner = [System.Management.Automation.Host.BufferCell]::new([char]0x2518, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $t90 = [System.Management.Automation.Host.BufferCell]::new([char]0x2524, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) $t270 = [System.Management.Automation.Host.BufferCell]::new([char]0x251C, $this._foregroundColor, $this._backgroundColor, ([System.Management.Automation.Host.BufferCellType]::Complete)) if ($this.Title) { Assert ($this.Title.Length -lt ($windowWidth - 3)) "title too wide for window" $t = $this.Title.Substring(0, [math]::Min($this.Title.Length, $windowWidth - 4)) # <-a-> <-l-> <-b--> # ┌─────┤abcde├──────┐ [int] $a = 0 [int] $b = 0 if (IsEven $windowWidth) { if (IsEven $t.Length) { # 01234567890123456789 # ┌──────┤abcd├──────┐ $a = $b = ($windowWidth - 4 - $t.Length) / 2 } else { # 01234567890123456789 # ┌──────┤abc├───────┐ $a = [int](($windowWidth - 4 - $t.Length) / 2) $b = [int](($windowWidth - 3 - $t.Length) / 2) } } else { # odd window width if (IsEven $t.Length) { # 0123456789012345678 # ┌─────┤abcd├──────┐ $a = [int](($windowWidth - 4 - $t.Length) / 2) $b = [int](($windowWidth - 3 - $t.Length) / 2) } else { # 0123456789012345678 # ┌──────┤abc├──────┐ $a = $b = ($windowWidth - 4 - $t.Length) / 2 } } $x = 1 for ($i = 0; $i -lt $a; ++$i) { $buffer[0, $x++] = $horizontalBar } $buffer[0, $x++]= $t90 for ($i = 0; $i -lt $t.Length; ++$i) { $buffer[0, $x++] = [System.Management.Automation.Host.BufferCell]::new($t[$i], $this._foregroundColor, $this._backgroundColor, ([Management.Automation.Host.BufferCellType]::Complete)) } $buffer[0, $x++]= $t270 for ($i = 0; $i -lt $b; ++$i) { $buffer[0, $x++] = $horizontalBar } } else { for ($i = 1; $i -lt ($this._rect.Right - $this._rect.Left); ++$i) { $buffer[0, $i] = $horizontalBar } } for ($i = 1; $i -lt ($this._rect.Right - $this._rect.Left); ++$i) { $buffer[($this._rect.Bottom - $this._rect.Top), $i] = $horizontalBar } for ($i = 1; $i -lt ($this._rect.Bottom - $this._rect.Top); ++$i) { $buffer[$i, 0] = $verticalBar } for ($i = 1; $i -lt ($this._rect.Bottom - $this._rect.Top); ++$i) { $buffer[$i, ($this._rect.Right - $this._rect.Left)] = $verticalBar } $buffer[0, 0] = $topLeftCorner $buffer[0, ($this._rect.Right - $this._rect.Left)] = $topRightCorner $buffer[($this._rect.Bottom - $this._rect.Top), 0] = $bottomLeftCorner $buffer[($this._rect.Bottom - $this._rect.Top), ($this._rect.Right - $this._rect.Left)] = $bottomRightCorner $Global:Host.UI.RawUI.SetBufferContents($this.WindowCoordinates(), $buffer) } hidden [void] DrawClientArea() {} hidden [void] SaveOriginalWindowArea() { $this._originalBufferContent = $Global:Host.UI.RawUI.GetBufferContents($this._rect) } hidden [void] RestoreOriginalWindowArea() { $Global:Host.UI.RawUI.SetBufferContents($this.WindowCoordinates(), $this._originalBufferContent) } hidden [void] OnShown() {} hidden [void] OnKey([System.ConsoleKeyInfo] $key) { # [Log]::Comment("Window.OnKey: Key=$($key.Key), Modifiers=$($key.Modifiers), KeyChar=$($key.KeyChar)") # We do not receive: # Alt+Home # Ctrl+Home # Shift+Home # Alt+(Left|Right|Up|Down) # Ctrl+(Left|Right|Up|Down) # Shift+(Left|Right|Up|Down) switch ($key.Key) { ([ConsoleKey]::Escape) { $this._running = $false $this.Result = [WindowResult]::Cancel } ([ConsoleKey]::Enter) { $this._running = $false $this.Result = [WindowResult]::OK } } } [WindowResult] $Result = [WindowResult]::OK [string] $Title = $null hidden [bool] $_running = $true hidden [System.Management.Automation.Host.Rectangle] $_rect hidden [System.ConsoleColor] $_foregroundColor hidden [System.ConsoleColor] $_backgroundColor hidden [System.Management.Automation.Host.BufferCell[,]] $_originalBufferContent } |