Gumby.ScrollView.psm1

using module Gumby.Debug
using module Gumby.Log
using module Gumby.TextBuffer
using module Window

class ScrollView : Window {
    ScrollView(
        [int] $left,
        [int] $top,
        [int] $width,
        [int] $height,
        
        [ConsoleColor] $foregroundColor = $Global:Host.UI.RawUI.BackgroundColor,
        [ConsoleColor] $backgroundColor = $Global:Host.UI.RawUI.ForegroundColor
    ) : base($left, $top, $width, $height, $foregroundColor, $backgroundColor) {

        $this._textBuffer = [TextBuffer]::new($foregroundColor, $backgroundColor)
    }

    [int] $FirstColumnInView = 0
    [int] $FirstRowInView = 0

    [int] LineCount() { return $this._textBuffer.LineCount() }

    [void] AddLine([string] $text, [ConsoleColor] $foregroundColor, [ConsoleColor] $backgroundColor) {
        $this._textBuffer.AddLine($text, $foregroundColor, $backgroundColor)
    }

    [void] InsertLine([int] $lineNumber, [string] $text, [ConsoleColor] $foregroundColor, [ConsoleColor] $backgroundColor) {
        # FirstRowInView should remain valid under all circumstances
        $this._textBuffer.InsertLine($lineNumber, $text, $foregroundColor, $backgroundColor)
    }

    [object] GetLine([int] $lineNumber) {
        return $this._textBuffer.GetLine($lineNumber)
    }

    [void] RemoveLine([int] $lineNumber) {
        # if FirstRowInView < LineCount - 1:
        # if lineNumber <= FirstRowInView: line removal causes FirstRowInView to point to the next line
        # if lineNumber > FirstRowInView: line removal does not affect validity of FirstRowInView
        if ($this.FirstRowInView -eq ($this.LineCount() - 1)) { $this.FirstRowInView = -1 }

        $this._textBuffer.RemoveLine($lineNumber)
    }

    [void] ClearLines() {
        $this._textBuffer.ClearLines()
        $this.FirstColumnInView = 0
        $this.FirstRowInView = 0
    }

    hidden [void] DrawClientArea() {
        $this._textBuffer.SetScreenBuffer($this.ClientRectangle(), [System.Management.Automation.Host.Coordinates]::new($this.FirstColumnInView, $this.FirstRowInView))
    }

    hidden [void] DrawLine([int] $lineNumber) {
        Assert ($lineNumber -ge $this.FirstRowInView) "trying to draw a line that is scrolled out of view"

        $lineRect = $this.ClientRectangle()
        $lineRect.Top += ($lineNumber - $this.FirstRowInView)
        $lineRect.Bottom = $lineRect.Top

        $this._textBuffer.SetScreenBuffer($lineRect, [System.Management.Automation.Host.Coordinates]::new($this.FirstColumnInView, $lineNumber))
    }

    hidden [void] OnKey([System.ConsoleKeyInfo] $key) {
        #[Log]::Comment("SV.OnKey: Key=$($key.Key), Modifiers=$($key.Modifiers)")
        switch ($key.Key) {
            ([System.ConsoleKey]::A) {
                if ($this.FirstColumnInView -eq 0) { break }
                --$this.FirstColumnInView
                $this.DrawClientArea()
            }

            ([System.ConsoleKey]::D) {
                if ($this.FirstColumnInView + $this.ClientWidth() -eq $this._textBuffer.ColumnCount()) { break }
                ++$this.FirstColumnInView
                $this.DrawClientArea()
            }

            ([System.ConsoleKey]::W) {
                if ($this.FirstRowInView -eq 0) { break }
                --$this.FirstRowInView
                $this.DrawClientArea()
            }

            ([System.ConsoleKey]::X) {
                if ($this.FirstRowInView + $this.ClientHeight() -eq $this._textBuffer.LineCount()) { break }
                ++$this.FirstRowInView
                $this.DrawClientArea()
            }

            ([System.ConsoleKey]::Home) {
                # We do not receive Alt+Home, Ctrl+Home and Shift+Home :(
                $this.FirstColumnInView = 0
                if ($key.Modifiers -eq ([System.ConsoleModifiers]::Control)) {
                    $this.FirstRowInView = 0
                }
                $this.DrawClientArea()
            }

            # Alternative for 'Home' key to enable modifier key combinations.
            ([System.ConsoleKey]::Q) {
                $this.FirstColumnInView = 0
                if ($key.Modifiers -eq ([System.ConsoleModifiers]::Control)) {
                    $this.FirstRowInView = 0
                }
                $this.DrawClientArea()
            }

            ([System.ConsoleKey]::End) {
                # We do not receive Alt+End, Ctrl+End and Shift+End :(
                $this.FirstColumnInView = [System.Math]::Max(0, $this._textBuffer.ColumnCount() - $this.ClientWidth())
                $this.FirstRowInView = [System.Math]::Max(0, $this._textBuffer.LineCount() - $this.ClientHeight())
                $this.DrawClientArea()
            }

            # Alternative for 'End' key to enable modifier key combinations.
            ([System.ConsoleKey]::Z) {
                $this.FirstColumnInView = [System.Math]::Max(0, $this._textBuffer.ColumnCount() - $this.ClientWidth())
                if ($key.Modifiers -eq ([System.ConsoleModifiers]::Control)) {
                    $this.FirstRowInView = [System.Math]::Max(0, $this._textBuffer.LineCount() - $this.ClientHeight())
                }
                $this.DrawClientArea()
            }

            default {
                ([Window]$this).OnKey($key)
            }
        }
    }

    hidden [TextBuffer] $_textBuffer
}