clui/canvas.go

330 lines
8.4 KiB
Go

package clui
import (
xs "github.com/huandu/xstrings"
term "github.com/nsf/termbox-go"
)
/*
FrameBuffer represents an object visible content. Used by Composer to
keep all screen info and by View. All methods of FrameBuffer use relative
corrdinate system that starts at left top FrameBuffer corner as 0,0
*/
type FrameBuffer struct {
buffer [][]term.Cell
w, h int
}
// NewFrameBuffer creates new buffer. Width and height of a new buffer cannot be less than 3
func NewFrameBuffer(w, h int) *FrameBuffer {
if w < 3 {
w = 3
}
if h < 3 {
h = 3
}
c := new(FrameBuffer)
c.SetSize(w, h)
return c
}
// Size returns current size
func (fb *FrameBuffer) Size() (width int, height int) {
return fb.w, fb.h
}
/*
SetSize sets the new FrameBuffer size. If new size does not equal old size then
FrameBuffer is recreated and cleared with default colors. Both FrameBuffer width and
height must be greater than 2
*/
func (fb *FrameBuffer) SetSize(w, h int) {
if w < 3 {
w = 3
}
if h < 3 {
h = 3
}
if w == fb.w && h == fb.h {
return
}
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)
}
}
// Clear fills FrameBuffer with given 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
}
}
}
// FillRect fills area of FrameBuffer with user-defined rune and colors
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
}
}
}
// Symbol returns current FrameBuffer cell value at given coordinates.
// If coordinates are outside FrameBuffer ok is false
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
}
return fb.buffer[y][x], true
}
// PutSymbol sets value for the FrameBuffer cell: rune and its colors. Returns result of operation: e.g, if the symbol position is outside FrameBuffer the operation fails and the function returns false
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
}
// PutChar sets value for the FrameBuffer cell: rune and its colors. Returns result of operation: e.g, if the symbol position is outside FrameBuffer the operation fails and the function returns false
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
}
// PutText draws horizontal string on FrameBuffer clipping by FrameBuffer boundaries. x and y are starting point, text is a string to display, fg and bg are text and background attributes
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}
if y >= 0 && y < fb.h && x+dx >= 0 && x+dx < fb.w {
fb.buffer[y][x+dx] = s
}
dx++
}
}
// PutVerticalText draws vertical string on FrameBuffer clipping by
// FrameBuffer boundaries. x and y are starting point, text is a string
// to display, fg and bg are text and background attributes
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++
}
}
/*
PutColorizedText draws multicolor string on Canvas clipping by Canvas boundaries.
Multiline is not supported.
Various parts of text can be colorized with html-like tags. Every tag must start with '<'
followed by tag type and colon(without space between them), atrribute in human redable form,
and closing '>'.
Available tags are:
'f' & 't' - sets new text color
'b' - sets new background color
Available attributes (it is possible to write a few attributes for one tag separated with space):
empty string - reset the color to default value (that is passed as argument)
'bold' or 'bright' - bold or brigther color(depends on terminal)
'underline' and 'underlined' - underined text(not every terminal can do it)
'reversed' - reversed text and background
other available attributes are color names: black, red, green, yellow, blue, magenta, cyan, white.
Example: PutColorizedText(0, 0, 10, "<t:red bold><b:yellow>E<t:>xample, ColorBlack, ColorWhite, Horizontal)
It displays red letter 'C' on a yellow background, then switch text color to default one and draws
other letters in black text and yellow background colors. Default background color is not used, so
it can be set as ColroDefault in a method call
*/
func (fb *FrameBuffer) PutColorizedText(x, y, max int, text string, fg, bg term.Attribute, dir Direction) {
var dx, dy int
if dir == Horizontal {
dx = 1
} else {
dy = 1
}
parser := NewColorParser(text, fg, bg)
elem := parser.NextElement()
for elem.Type != ElemEndOfText && max > 0 {
if elem.Type == ElemPrintable {
fb.PutChar(x, y, elem.Ch, elem.Fg, elem.Bg)
x += dx
y += dy
max--
}
elem = parser.NextElement()
}
}
/*
DrawFrame paints a frame inside FrameBuffer with optional border
rune set(by default, in case of border is empty string, the rune set
equals "─│┌┐└┘" - single border). The inner area of frame is not
filled - in other words it is transparent
*/
func (fb *FrameBuffer) DrawFrame(x, y, w, h int, fg, bg term.Attribute, frameChars string) {
if h < 1 || w < 1 {
return
}
if xs.Len(frameChars) < 6 {
frameChars = "─│┌┐└┘"
}
parts := []rune(frameChars)
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)
}
}
/*
SetCursorPos sets text caret position. In opposite to other FrameBuffer
methods, x and y - are absolute console coordinates. Use negative values
if you want to hide the caret. Used by controls like EditField
*/
func (fb *FrameBuffer) SetCursorPos(x, y int) {
term.SetCursor(x, y)
}
/*
DrawScroll paints a scroll bar inside FrameBuffer.
x, y - start position.
w, h - width and height (if h equals 1 then horizontal scroll is drawn
and vertical otherwise).
pos - thumb position.
fgScroll, bgScroll - scroll bar main attributes.
fgThumb, bgThumb - thumb colors.
scrollChars - rune set(by default, in case of is is empty string, the
rune set equals "░■▲▼")
*/
func (fb *FrameBuffer) DrawScroll(x, y, w, h, pos int, fgScroll, bgScroll, fgThumb, bgThumb term.Attribute, scrollChars string) {
if w < 1 || h < 1 {
return
}
if scrollChars == "" {
scrollChars = "░■▲▼◄►"
}
parts := []rune(scrollChars)
chLine, chCursor, chUp, chDown := parts[0], parts[1], parts[2], parts[3]
chLeft, chRight := '◄', '►'
if len(parts) > 4 {
chLeft, chRight = parts[4], parts[5]
}
if h == 1 {
fb.PutChar(x, y, chLeft, fgScroll, bgScroll)
fb.PutChar(x+w-1, y, chRight, fgScroll, bgScroll)
if w > 2 {
for xx := 1; xx < w-1; xx++ {
fb.PutChar(x+xx, y, chLine, fgScroll, bgScroll)
}
}
if pos != -1 {
fb.PutChar(x+pos, y, chCursor, fgThumb, bgThumb)
}
} else {
fb.PutChar(x, y, chUp, fgScroll, bgScroll)
fb.PutChar(x, y+h-1, chDown, fgScroll, bgScroll)
if h > 2 {
for yy := 1; yy < h-1; yy++ {
fb.PutChar(x, y+yy, chLine, fgScroll, bgScroll)
}
}
if pos != -1 {
fb.PutChar(x, y+pos, chCursor, fgThumb, bgThumb)
}
}
}