package goterm import ( "bytes" "regexp" "strings" _ "unicode/utf8" ) const DEFAULT_BORDER = "- │ ┌ ┐ └ ┘" // Box allows you to create independent parts of screen, with its own buffer and borders. // Can be used for creating modal windows // // Generates boxes likes this: // ┌--------┐ // │hello │ // │world │ // │ │ // └--------┘ // type Box struct { Buf *bytes.Buffer Width int Height int // To get even padding: PaddingX ~= PaddingY*4 PaddingX int PaddingY int // Should contain 6 border pieces separated by spaces // // Example border: // "- │ ┌ ┐ └ ┘" Border string Flags int // Not used now } // Create new Box. // Width and height can be relative: // // // Create box with 50% with of current screen and 10 lines height // box := tm.NewBox(50|tm.PCT, 10, 0) // func NewBox(width, height int, flags int) *Box { width, height = GetXY(width, height) box := new(Box) box.Buf = new(bytes.Buffer) box.Width = width box.Height = height box.Border = DEFAULT_BORDER box.PaddingX = 1 box.PaddingY = 0 box.Flags = flags return box } func (b *Box) Write(p []byte) (int, error) { return b.Buf.Write(p) } var ANSI_RE = regexp.MustCompile(`\\0\d+\[\d+(?:;\d+)?m`) // String renders Box func (b *Box) String() (out string) { borders := strings.Split(b.Border, " ") lines := strings.Split(b.Buf.String(), "\n") // Border + padding prefix := borders[1] + strings.Repeat(" ", b.PaddingX) suffix := strings.Repeat(" ", b.PaddingX) + borders[1] offset := b.PaddingY + 1 // 1 is border width // Content width without borders and padding contentWidth := b.Width - (b.PaddingX+1)*2 for y := 0; y < b.Height; y++ { var line string switch { // Draw borders for first line case y == 0: line = borders[2] + strings.Repeat(borders[0], b.Width-2) + borders[3] // Draw borders for last line case y == (b.Height - 1): line = borders[4] + strings.Repeat(borders[0], b.Width-2) + borders[5] // Draw top and bottom padding case y <= b.PaddingY || y >= (b.Height-b.PaddingY): line = borders[1] + strings.Repeat(" ", b.Width-2) + borders[1] // Render content default: if len(lines) > y-offset { line = lines[y-offset] } else { line = "" } r := []rune(line) lastAnsii := "" withoutAnsii := []rune{} withOffset := []rune{} i := 0 for { if i >= len(r) { break } if r[i] == 27 { lastAnsii = "" withOffset = append(withOffset, r[i]) lastAnsii += string(r[i]) i++ for { i++ if i > len(r) { break } withOffset = append(withOffset, r[i]) lastAnsii += string(r[i]) if r[i] == 'm' { i++ break } } } if i >= len(r) { break } withoutAnsii = append(withoutAnsii, r[i]) if len(withoutAnsii) <= contentWidth { withOffset = append(withOffset, r[i]) } i++ } if len(withoutAnsii) > contentWidth { // If line is too large limit it line = string(withOffset) } else { // If line is too small enlarge it by adding spaces line += strings.Repeat(" ", contentWidth-len(withoutAnsii)) } if lastAnsii != "" { line += RESET } line = prefix + line + suffix } // Don't add newline for last element if y != b.Height-1 { line += "\n" } out += line } return out }