2015-10-16 10:27:43 -07:00
|
|
|
package clui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
xs "github.com/huandu/xstrings"
|
|
|
|
term "github.com/nsf/termbox-go"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Represents an object visible content. Used by Composer to keep all screen info and by Window
|
|
|
|
type FrameBuffer struct {
|
|
|
|
buffer [][]term.Cell
|
|
|
|
w, h int
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates new buffer
|
|
|
|
func NewFrameBuffer(w, h int) *FrameBuffer {
|
|
|
|
if w < 5 || h < 5 {
|
|
|
|
panic(fmt.Sprintf("Invalid size: %vx%v.", w, h))
|
|
|
|
}
|
|
|
|
|
|
|
|
c := new(FrameBuffer)
|
|
|
|
c.SetSize(w, h)
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns current width and height of a buffer
|
|
|
|
func (fb *FrameBuffer) Size() (int, int) {
|
|
|
|
return fb.w, fb.h
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) SetSize(w, h int) {
|
|
|
|
if w == fb.w && h == fb.h {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if w < 5 || h < 5 {
|
|
|
|
panic(fmt.Sprintf("Invalid size: %vx%v.", w, h))
|
|
|
|
}
|
|
|
|
|
|
|
|
fb.w, fb.h = w, h
|
|
|
|
|
|
|
|
fb.buffer = make([][]term.Cell, h)
|
|
|
|
for i := 0; i < h; i++ {
|
|
|
|
fb.buffer[i] = make([]term.Cell, w)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fills buffers with color (the buffer is filled with spaces with defined background color)
|
|
|
|
func (fb *FrameBuffer) Clear(bg term.Attribute) {
|
|
|
|
s := term.Cell{Ch: ' ', Fg: ColorWhite, Bg: bg}
|
|
|
|
for y := 0; y < fb.h; y++ {
|
|
|
|
for x := 0; x < fb.w; x++ {
|
|
|
|
fb.buffer[y][x] = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) FillRect(x, y, w, h int, s term.Cell) {
|
|
|
|
if x < 0 {
|
|
|
|
w += x
|
|
|
|
x = 0
|
|
|
|
}
|
|
|
|
if y < 0 {
|
|
|
|
h += y
|
|
|
|
y = 0
|
|
|
|
}
|
|
|
|
if x+w >= fb.w {
|
|
|
|
w = fb.w - x
|
|
|
|
}
|
|
|
|
if y+h >= fb.h {
|
|
|
|
h = fb.h - y
|
|
|
|
}
|
|
|
|
|
|
|
|
for yy := y; yy < y+h; yy++ {
|
|
|
|
for xx := x; xx < x+w; xx++ {
|
|
|
|
fb.buffer[yy][xx] = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) Symbol(x, y int) (term.Cell, bool) {
|
|
|
|
if x >= fb.w || x < 0 || y >= fb.h || y < 0 {
|
|
|
|
return term.Cell{Ch: ' '}, false
|
|
|
|
} else {
|
|
|
|
return fb.buffer[y][x], true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) PutSymbol(x, y int, s term.Cell) bool {
|
|
|
|
if x < 0 || x >= fb.w || y < 0 || y >= fb.h {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fb.buffer[y][x] = s
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) PutChar(x, y int, c rune, fg, bg term.Attribute) bool {
|
|
|
|
if x < 0 || x >= fb.w || y < 0 || y >= fb.h {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fb.buffer[y][x] = term.Cell{Ch: c, Fg: fg, Bg: bg}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draws text line on buffer
|
|
|
|
func (fb *FrameBuffer) PutText(x, y int, text string, fg, bg term.Attribute) {
|
|
|
|
width := fb.w
|
|
|
|
|
|
|
|
if (x < 0 && xs.Len(text) <= -x) || x >= fb.w || y < 0 || y >= fb.h {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if x < 0 {
|
|
|
|
xx := -x
|
|
|
|
x = 0
|
|
|
|
text = xs.Slice(text, xx, -1)
|
|
|
|
}
|
|
|
|
text = CutText(text, width)
|
|
|
|
|
|
|
|
dx := 0
|
|
|
|
for _, char := range text {
|
|
|
|
s := term.Cell{Ch: char, Fg: fg, Bg: bg}
|
|
|
|
fb.buffer[y][x+dx] = s
|
|
|
|
dx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-19 14:51:18 -07:00
|
|
|
// Draws vertical text line on buffer
|
|
|
|
func (fb *FrameBuffer) PutVerticalText(x, y int, text string, fg, bg term.Attribute) {
|
|
|
|
height := fb.h
|
|
|
|
|
|
|
|
if (y < 0 && xs.Len(text) <= -y) || x < 0 || y < 0 || x >= fb.w {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if y < 0 {
|
|
|
|
yy := -y
|
|
|
|
y = 0
|
|
|
|
text = xs.Slice(text, yy, -1)
|
|
|
|
}
|
|
|
|
text = CutText(text, height)
|
|
|
|
|
|
|
|
dy := 0
|
|
|
|
for _, char := range text {
|
|
|
|
s := term.Cell{Ch: char, Fg: fg, Bg: bg}
|
|
|
|
fb.buffer[y+dy][x] = s
|
|
|
|
dy++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-16 10:27:43 -07:00
|
|
|
// Draws vertical text line on buffer
|
|
|
|
func (fb *FrameBuffer) PutTextVertical(x, y int, text string, fg, bg term.Attribute) {
|
|
|
|
height := fb.h
|
|
|
|
|
|
|
|
if (y < 0 && xs.Len(text) <= -y) || x >= fb.w || y > 0 || y >= fb.h {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if y < 0 {
|
|
|
|
yy := -y
|
|
|
|
y = 0
|
|
|
|
text = xs.Slice(text, yy, -1)
|
|
|
|
}
|
|
|
|
text = CutText(text, height)
|
|
|
|
|
|
|
|
dy := 0
|
|
|
|
for _, char := range text {
|
|
|
|
s := term.Cell{Ch: char, Fg: fg, Bg: bg}
|
|
|
|
fb.buffer[y+dy][x] = s
|
|
|
|
dy++
|
|
|
|
}
|
2015-10-26 11:33:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) PutColorizedText(x, y, max int, text string, fg, bg term.Attribute, dir Direction, align Align) {
|
|
|
|
// file, _ := os.OpenFile("debugui.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
|
|
// logger := log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)
|
|
|
|
|
|
|
|
sReal := UnColorizeText(text)
|
|
|
|
var dx, dy int
|
|
|
|
if dir == Horizontal {
|
|
|
|
delta, _ := AlignText(sReal, max, align)
|
|
|
|
x += delta
|
|
|
|
dx = 1
|
|
|
|
} else {
|
|
|
|
delta, _ := AlignText(sReal, max, align)
|
|
|
|
y += delta
|
|
|
|
dy = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
parser := NewColorParser(text, fg, bg)
|
|
|
|
elem := parser.NextElement()
|
|
|
|
for elem.Type != ElemEndOfText && max > 0 {
|
|
|
|
// logger.Printf("ELEM: %v", elem)
|
|
|
|
if elem.Type == ElemPrintable {
|
|
|
|
fb.PutChar(x, y, elem.Ch, elem.Fg, elem.Bg)
|
|
|
|
x += dx
|
|
|
|
y += dy
|
|
|
|
max--
|
|
|
|
}
|
|
|
|
|
|
|
|
elem = parser.NextElement()
|
|
|
|
}
|
2015-10-16 10:27:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *FrameBuffer) DrawFrame(x, y, w, h int, fg, bg term.Attribute, frameChars string) {
|
|
|
|
if h < 1 || w < 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if frameChars == "" {
|
|
|
|
frameChars = "─│┌┐└┘"
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := []rune(frameChars)
|
|
|
|
if len(parts) < 6 {
|
|
|
|
panic("Invalid theme: single border")
|
|
|
|
}
|
|
|
|
|
|
|
|
H, V, UL, UR, DL, DR := parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]
|
|
|
|
|
|
|
|
if h == 1 {
|
|
|
|
for xx := x; xx < x+w; xx++ {
|
|
|
|
fb.PutChar(xx, y, H, fg, bg)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if w == 1 {
|
|
|
|
for yy := y; yy < y+h; yy++ {
|
|
|
|
fb.PutChar(x, yy, V, fg, bg)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fb.PutChar(x, y, UL, fg, bg)
|
|
|
|
fb.PutChar(x, y+h-1, DL, fg, bg)
|
|
|
|
fb.PutChar(x+w-1, y, UR, fg, bg)
|
|
|
|
fb.PutChar(x+w-1, y+h-1, DR, fg, bg)
|
|
|
|
|
|
|
|
for xx := x + 1; xx < x+w-1; xx++ {
|
|
|
|
fb.PutChar(xx, y, H, fg, bg)
|
|
|
|
fb.PutChar(xx, y+h-1, H, fg, bg)
|
|
|
|
}
|
|
|
|
for yy := y + 1; yy < y+h-1; yy++ {
|
|
|
|
fb.PutChar(x, yy, V, fg, bg)
|
|
|
|
fb.PutChar(x+w-1, yy, V, fg, bg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// x, y - absolute console coordinates
|
|
|
|
func (fb *FrameBuffer) SetCursorPos(x, y int) {
|
|
|
|
term.SetCursor(x, y)
|
|
|
|
}
|