2022-10-31 13:51:28 -06:00
|
|
|
package stone
|
|
|
|
|
2022-11-15 15:45:48 -07:00
|
|
|
import "sync"
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Color represents all the different colors a cell can be.
|
2022-10-31 13:51:28 -06:00
|
|
|
type Color uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
ColorBackground Color = 0x0
|
2022-11-15 09:16:29 -07:00
|
|
|
ColorForeground Color = 0x1
|
2022-11-18 17:46:50 -07:00
|
|
|
ColorDim Color = 0x2
|
|
|
|
ColorRed Color = 0x3
|
2022-11-15 09:16:29 -07:00
|
|
|
ColorYellow Color = 0x4
|
|
|
|
ColorGreen Color = 0x5
|
|
|
|
ColorBlue Color = 0x6
|
|
|
|
ColorPurple Color = 0x7
|
2022-10-31 13:51:28 -06:00
|
|
|
)
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Style contains styling information about cells. These properties can be or'd
|
|
|
|
// together to combine them.
|
2022-10-31 13:51:28 -06:00
|
|
|
type Style uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
StyleNormal Style = iota
|
|
|
|
StyleBold Style = iota >> 1
|
|
|
|
StyleItalic
|
2022-11-17 09:25:27 -07:00
|
|
|
StyleUnderline
|
|
|
|
StyleHighlight
|
2022-10-31 13:51:28 -06:00
|
|
|
StyleBoldItalic Style = StyleBold | StyleItalic
|
|
|
|
)
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Cell is a grid-aligned rune in a buffer with associated styling and color
|
|
|
|
// informaiton.
|
2022-10-31 13:51:28 -06:00
|
|
|
type Cell struct {
|
|
|
|
color Color
|
|
|
|
style Style
|
|
|
|
content rune
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Color returns the cell's color.
|
2022-11-15 09:16:29 -07:00
|
|
|
func (cell Cell) Color () (color Color) {
|
2022-10-31 13:51:28 -06:00
|
|
|
color = cell.color
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Style returns the styling information associated with the cell
|
2022-11-28 22:50:23 -07:00
|
|
|
func (cell Cell) Style () (style Style) {
|
2022-10-31 13:51:28 -06:00
|
|
|
style = cell.style
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Rune returns the rune in the cell
|
2022-11-02 16:51:33 -06:00
|
|
|
func (cell Cell) Rune () (content rune) {
|
2022-10-31 13:51:28 -06:00
|
|
|
content = cell.content
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-16 19:20:48 -07:00
|
|
|
// 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 {
|
2022-11-16 22:26:28 -07:00
|
|
|
content []Cell
|
|
|
|
onScreen []Cell
|
2022-11-15 15:36:41 -07:00
|
|
|
|
2022-10-31 13:51:28 -06:00
|
|
|
width int
|
|
|
|
height int
|
2022-11-15 15:41:08 -07:00
|
|
|
dot struct {
|
|
|
|
x int
|
|
|
|
y int
|
2022-11-06 12:17:43 -07:00
|
|
|
}
|
2022-11-15 15:45:48 -07:00
|
|
|
|
|
|
|
// 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
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) isOutOfBounds (x, y int) (outOfBounds bool) {
|
2022-11-06 13:25:55 -07:00
|
|
|
outOfBounds =
|
|
|
|
x < 0 ||
|
|
|
|
y < 0 ||
|
|
|
|
x >= buffer.width ||
|
|
|
|
y >= buffer.height
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Size returns the width and height of the buffer.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) Size () (width, height int) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-10-31 13:51:28 -06:00
|
|
|
width = buffer.width
|
|
|
|
height = buffer.height
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-15 15:41:08 -07:00
|
|
|
// SetDot sets the buffer's text insertion position relative to the buffer
|
|
|
|
// origin point (0, 0).
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) SetDot (x, y int) {
|
2022-11-15 15:41:08 -07:00
|
|
|
buffer.dot.x = x
|
|
|
|
buffer.dot.y = y
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// 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.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) Cell (x, y int) (cell Cell) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-10-31 13:51:28 -06:00
|
|
|
cell = buffer.content[x + y * buffer.width]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// SetColor sets the color of the cell at the specified x and y coordinates.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) SetColor (x, y int, color Color) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
buffer.content[x + y * buffer.width].color = color
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// 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.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) SetSize (width, height int) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.Lock()
|
|
|
|
defer buffer.lock.Unlock()
|
|
|
|
|
2022-11-06 13:59:06 -07:00
|
|
|
if width < 0 || height < 0 { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
buffer.width = width
|
|
|
|
buffer.height = height
|
|
|
|
buffer.content = make([]Cell, width * height)
|
|
|
|
buffer.onScreen = make([]Cell, width * height)
|
2022-11-15 15:36:41 -07:00
|
|
|
for index := 0; index < len(buffer.content); index ++ {
|
|
|
|
buffer.content[index].color = ColorForeground
|
|
|
|
}
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// SetStyle sets the style of the cell at the specified x and y coordinates.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) SetStyle (x, y int, style Style) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
buffer.content[x + y * buffer.width].style = style
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// SetRune sets the rune of the cell at the specified x and y coordinates.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) SetRune (x, y int, content rune) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-15 22:29:23 -07:00
|
|
|
buffer.setRune(x, y, content)
|
|
|
|
}
|
|
|
|
|
2022-11-16 19:20:48 -07:00
|
|
|
// 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) {
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
buffer.content[x + y * buffer.width].content = content
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Write writes data stored in a byte slice to the buffer at the current dot
|
2022-11-15 15:36:41 -07:00
|
|
|
// position. This makes Buffer an io.Writer.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) Write (bytes []byte) (bytesWritten int, err error) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 12:17:43 -07:00
|
|
|
text := string(bytes)
|
|
|
|
bytesWritten = len(bytes)
|
|
|
|
|
|
|
|
for _, character := range text {
|
2022-11-26 20:49:58 -07:00
|
|
|
if character == '\n' {
|
|
|
|
buffer.dot.x = 0
|
|
|
|
buffer.dot.y ++
|
|
|
|
} else {
|
|
|
|
buffer.setRune(buffer.dot.x, buffer.dot.y, character)
|
|
|
|
buffer.dot.x ++
|
|
|
|
}
|
2022-11-06 12:17:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-14 22:22:01 -07:00
|
|
|
// Clean returns whether or not the cell at the specified x and y coordinates is
|
|
|
|
// clean.
|
2022-11-16 19:20:48 -07:00
|
|
|
func (buffer *DamageBuffer) Clean (x, y int) (clean bool) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
index := x + y * buffer.width
|
|
|
|
clean = buffer.content[index] == buffer.onScreen[index]
|
2022-10-31 13:51:28 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-16 22:26:28 -07:00
|
|
|
// 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) {
|
2022-11-15 15:45:48 -07:00
|
|
|
buffer.lock.RLock()
|
|
|
|
defer buffer.lock.RUnlock()
|
|
|
|
|
2022-11-06 13:25:55 -07:00
|
|
|
if buffer.isOutOfBounds(x, y) { return }
|
2022-11-16 22:26:28 -07:00
|
|
|
index := x + y * buffer.width
|
|
|
|
buffer.onScreen[index] = buffer.content[index]
|
|
|
|
cell = buffer.content[index]
|
|
|
|
return
|
2022-10-31 13:51:28 -06:00
|
|
|
}
|