package buf2d import ( "strings" ) // Buffer is a 2-dimensional buffer type Buffer[T any] struct { data [][]T x, y int width int height int parent *Buffer[T] StringFunc func(T) string } // NewBuffer makes a new buffer with the given dimensions func NewBuffer[T any](width, height int, defaultValue T) *Buffer[T] { b := make([][]T, height) for y := range b { b[y] = make([]T, width) for x := range b[y] { b[y][x] = defaultValue } } return &Buffer[T]{ x: 0, y: 0, data: b, width: width, height: height, parent: nil, StringFunc: MakeDefaultStringFunc[T](), } } func (b *Buffer[T]) limX(x int) int { return limit(x, 0, b.width-1) } func (b *Buffer[T]) limY(y int) int { return limit(y, 0, b.height-1) } // Set sets the value at position (x,y) to c func (b *Buffer[T]) Set(x, y int, v T) { if b.width > 0 && b.height > 0 { b.data[b.limY(y)][b.limX(x)] = v } } // Get returns the value at position (x,y) func (b *Buffer[T]) Get(x, y int) T { return b.data[y][x] } // Size returns width and height of b func (b *Buffer[T]) Size() (w, h int) { return b.width, b.height } // Width returns the width of b func (b *Buffer[T]) Width() int { return b.width } // Height returns the height of b func (b *Buffer[T]) Height() int { return b.height } // Offset returns the offset of b relative to its parent buffer. // Offset returns zeros if b has no parent func (b *Buffer[T]) Offset() (x, y int) { return b.x, b.y } // OffsetX returns the horizontal offset of b relative to its parent buffer. // OffsetX returns 0 if b has no parent func (b *Buffer[T]) OffsetX() int { return b.x } // OffsetY returns the vertical offset of b relative to its parent buffer. // OffsetY returns 0 if b has no parent func (b *Buffer[T]) OffsetY() int { return b.y } // ForEachLine calls f for each line in b func (b *Buffer[T]) ForEachLine(f func(line int, content []T)) { for line, content := range b.data { f(line, content) } } // ForEach calls f for every value in b func (b *Buffer[T]) ForEach(f func(x, y int, v T)) { for y, col := range b.data { for x, v := range col { f(x, y, v) } } } func (b *Buffer[T]) String() string { s := new(strings.Builder) for ci, col := range b.data { for _, v := range col { s.WriteString(b.StringFunc(v)) } if ci != len(b.data)-1 { s.WriteRune('\n') } } return s.String() } // Sub returns a buffer which is completely contained in this buffer // Modifying the main buffer or the sub buffer will modify the other one as well // This method can be used recursively // If the given dimensions don't fit in the parent buffer, it will be truncated func (b *Buffer[T]) Sub(x, y, w, h int) *Buffer[T] { // sanitize inputs x = limit(x, 0, b.width-1) y = limit(y, 0, b.height-1) w = limit(w, 0, b.width-x) h = limit(h, 0, b.height-y) // make slice references data := make([][]T, h) for dy := 0; dy < h; dy++ { col := b.data[y+dy] data[dy] = col[x : x+w] } // make buffer return &Buffer[T]{ x: x, y: y, data: data, width: w, height: h, parent: b, StringFunc: b.StringFunc, } }