stone/buffer.go

231 lines
5.8 KiB
Go

package stone
import "sync"
// Color represents all the different colors a cell can be.
type Color uint8
const (
ColorBackground Color = 0x0
ColorForeground Color = 0x1
ColorDim Color = 0x2
ColorRed Color = 0x3
ColorYellow Color = 0x4
ColorGreen Color = 0x5
ColorBlue Color = 0x6
ColorPurple Color = 0x7
)
// Style contains styling information about cells. These properties can be or'd
// together to combine them.
type Style uint8
const (
StyleNormal Style = 0
StyleBold Style = 1
StyleItalic Style = 2
StyleUnderline Style = 4
StyleHighlight Style = 8
StyleBoldItalic Style = StyleBold | StyleItalic
)
// Cell is a grid-aligned rune in a buffer with associated styling and color
// informaiton.
type Cell struct {
color Color
style Style
content rune
}
// Color returns the cell's color.
func (cell Cell) Color () (color Color) {
color = cell.color
return
}
// Style returns the styling information associated with the cell
func (cell Cell) Style () (style Style) {
style = cell.style
return
}
// Rune returns the rune in the cell
func (cell Cell) Rune () (content rune) {
content = cell.content
return
}
// Buffer represents a two dimensional text buffer.
type Buffer interface {
Size () (with, height int)
Cell (x, y int) (cell Cell)
SetColor (x, y int, color Color)
SetSize (with, height int)
SetStyle (x, y int, style Style)
SetRune (x, y int, content rune)
Clear ()
}
// DamageBuffer is a two dimensional text buffer that stores a grid of cells, as
// well as information stating whether each cell is clean or dirty. Cells are
// dirty by default, are only clean when marked as clean, and become dirty again
// when they are altered in some way.
type DamageBuffer struct {
content []Cell
onScreen []Cell
width int
height int
dot struct {
x int
y int
}
// This should be write locked when resizing the buffer, and read locked
// when writing to cells or reading information about the buffer.
lock sync.RWMutex
}
func (buffer *DamageBuffer) isOutOfBounds (x, y int) (outOfBounds bool) {
outOfBounds =
x < 0 ||
y < 0 ||
x >= buffer.width ||
y >= buffer.height
return
}
// Size returns the width and height of the buffer.
func (buffer *DamageBuffer) Size () (width, height int) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
width = buffer.width
height = buffer.height
return
}
// SetDot sets the buffer's text insertion position relative to the buffer
// origin point (0, 0).
func (buffer *DamageBuffer) SetDot (x, y int) {
buffer.dot.x = x
buffer.dot.y = y
}
// Cell returns the cell at the specified x and y coordinates. If the
// coordinates are out of bounds, this method will return a blank cell.
func (buffer *DamageBuffer) Cell (x, y int) (cell Cell) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
if buffer.isOutOfBounds(x, y) { return }
cell = buffer.content[x + y * buffer.width]
return
}
// SetColor sets the color of the cell at the specified x and y coordinates.
func (buffer *DamageBuffer) SetColor (x, y int, color Color) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
if buffer.isOutOfBounds(x, y) { return }
buffer.content[x + y * buffer.width].color = color
}
// SetSize sets the width and height of the buffer. This clears all data in the
// buffer. If the width or height is negative, this method does nothing.
func (buffer *DamageBuffer) SetSize (width, height int) {
buffer.lock.Lock()
defer buffer.lock.Unlock()
if width < 0 || height < 0 { return }
buffer.width = width
buffer.height = height
buffer.content = make([]Cell, width * height)
buffer.onScreen = make([]Cell, width * height)
for index := 0; index < len(buffer.content); index ++ {
buffer.content[index].color = ColorForeground
}
}
// SetStyle sets the style of the cell at the specified x and y coordinates.
func (buffer *DamageBuffer) SetStyle (x, y int, style Style) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
if buffer.isOutOfBounds(x, y) { return }
buffer.content[x + y * buffer.width].style = style
}
// SetRune sets the rune of the cell at the specified x and y coordinates.
func (buffer *DamageBuffer) SetRune (x, y int, content rune) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
buffer.setRune(x, y, content)
}
// Clear resets the entire buffer.
func (buffer *DamageBuffer) Clear () {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
for index := 0; index < len(buffer.content); index ++ {
buffer.content[index] = Cell {
color: ColorForeground,
}
}
}
func (buffer *DamageBuffer) setRune (x, y int, content rune) {
if buffer.isOutOfBounds(x, y) { return }
buffer.content[x + y * buffer.width].content = content
}
// Write writes data stored in a byte slice to the buffer at the current dot
// position. This makes Buffer an io.Writer.
func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
text := string(bytes)
bytesWritten = len(bytes)
for _, character := range text {
if character == '\n' {
buffer.dot.x = 0
buffer.dot.y ++
} else {
buffer.setRune(buffer.dot.x, buffer.dot.y, character)
buffer.dot.x ++
}
}
return
}
// Clean returns whether or not the cell at the specified x and y coordinates is
// clean.
func (buffer *DamageBuffer) Clean (x, y int) (clean bool) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
if buffer.isOutOfBounds(x, y) { return }
index := x + y * buffer.width
clean = buffer.content[index] == buffer.onScreen[index]
return
}
// GetForRendering returns the cell at the specified x and y coordinates and
// marks it as clean.
func (buffer *DamageBuffer) GetForRendering (x, y int) (cell Cell) {
buffer.lock.RLock()
defer buffer.lock.RUnlock()
if buffer.isOutOfBounds(x, y) { return }
index := x + y * buffer.width
buffer.onScreen[index] = buffer.content[index]
cell = buffer.content[index]
return
}