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 }