diff --git a/buffer.go b/buffer.go index 352a9e8..2cd85fd 100644 --- a/buffer.go +++ b/buffer.go @@ -1,76 +1,72 @@ package buf2d import ( - "fmt" "strings" ) -// Buffer is a 2-dimensional rune buffer -type Buffer struct { - data [][]fmt.Stringer - width int - height int - parent *Buffer +// Buffer is a 2-dimensional buffer +type Buffer[T any] struct { + data [][]T + width int + height int + parent *Buffer[T] + StringFunc func(T) string } // NewBuffer makes a new buffer with the given dimensions -func NewBuffer(width, height int) *Buffer { - b := make([][]fmt.Stringer, height) +func NewBuffer[T any](width, height int, defaultValue T) *Buffer[T] { + b := make([][]T, height) for y := range b { - b[y] = make([]fmt.Stringer, width) + b[y] = make([]T, width) for x := range b[y] { - b[y][x] = spaceStringer + b[y][x] = defaultValue } } - return &Buffer{ - data: b, - width: width, - height: height, - parent: nil, + return &Buffer[T]{ + data: b, + width: width, + height: height, + parent: nil, + StringFunc: MakeDefaultStringFunc[T](), } } -func (b *Buffer) x(x int) int { +func (b *Buffer[T]) x(x int) int { return limit(x, 0, b.width-1) } -func (b *Buffer) y(y int) int { +func (b *Buffer[T]) y(y int) int { return limit(y, 0, b.height-1) } -// Set sets the fmt.Stringer at position (x,y) to c -func (b *Buffer) Set(x, y int, c fmt.Stringer) { - b.data[b.y(y)][b.x(x)] = c +// Set sets the value at position (x,y) to c +func (b *Buffer[T]) Set(x, y int, v T) { + b.data[b.y(y)][b.x(x)] = v } -// SetRune sets the fmt.Stringer at position (x,y) to rn -func (b *Buffer) SetRune(x, y int, rn rune) { - b.data[b.y(y)][b.x(x)] = newStringerFromRune(rn) -} - -// Get returns the fmt.Stringer at position (x,y) -func (b *Buffer) Get(x, y int) fmt.Stringer { +// 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) Size() (w, h int) { +func (b *Buffer[T]) Size() (w, h int) { return b.width, b.height } // Width returns the width of b -func (b *Buffer) Width() int { +func (b *Buffer[T]) Width() int { return b.width } // Height returns the height of b -func (b *Buffer) Height() int { +func (b *Buffer[T]) Height() int { return b.height } -// ForEach calls f for every rune in this buffer -func (b *Buffer) ForEach(f func(x, y int, c fmt.Stringer)) { +// ForEach calls f for every value in this buffer +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) @@ -78,11 +74,11 @@ func (b *Buffer) ForEach(f func(x, y int, c fmt.Stringer)) { } } -func (b *Buffer) String() string { +func (b *Buffer[T]) String() string { s := new(strings.Builder) for ci, col := range b.data { for _, v := range col { - s.WriteString(fmt.Sprintf("%v", v)) + s.WriteString(b.StringFunc(v)) } if ci != len(b.data)-1 { s.WriteRune('\n') @@ -95,7 +91,7 @@ func (b *Buffer) String() string { // 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) Sub(x, y, w, h int) *Buffer { +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) @@ -103,17 +99,18 @@ func (b *Buffer) Sub(x, y, w, h int) *Buffer { h = limit(h, 1, b.height-y) // make slice references - data := make([][]fmt.Stringer, h) + 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{ - data: data, - width: w, - height: h, - parent: b, + return &Buffer[T]{ + data: data, + width: w, + height: h, + parent: b, + StringFunc: b.StringFunc, } } diff --git a/buffer_test.go b/buffer_test.go index f1e96aa..2802ae2 100644 --- a/buffer_test.go +++ b/buffer_test.go @@ -7,13 +7,13 @@ import ( ) func TestSub(t *testing.T) { - b := NewBuffer(10, 10) + b := NewBuffer(10, 10, ' ') s := b.Sub(1, 1, b.Width()-1, b.Height()-1) - b.SetRune(5, 1, 'a') - s.SetRune(5, 5, 'b') - b.WriteString("Hello world", 1, 2) + b.Set(5, 1, 'a') + s.Set(5, 5, 'b') + WriteString(b, "Hello world", 1, 2) fmt.Println(b) - fmt.Println(strings.Repeat("-", 20)) + fmt.Println(strings.Repeat("-", 10)) fmt.Println(s) } diff --git a/go.mod b/go.mod index cf8fb4e..9add63c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module git.tordarus.net/Tordarus/buf2d -go 1.15 +go 1.18 diff --git a/utils.go b/utils.go index 7473b6c..a0e5203 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,9 @@ package buf2d -import "fmt" +import ( + "fmt" + "reflect" +) func limit(v, min, max int) int { return getmax(getmin(v, max), min) @@ -20,21 +23,18 @@ func getmin(x, y int) int { return y } -type stringerImpl string +func MakeDefaultStringFunc[T any]() func(T) string { + if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(rune)).Elem() { + return func(value T) string { + return string((interface{}(value)).(rune)) + } + } else if reflect.TypeOf(new(T)).Elem() == reflect.TypeOf(new(string)).Elem() { + return func(value T) string { + return (interface{}(value)).(string) + } + } -func (s *stringerImpl) String() string { - return string(*s) + return func(value T) string { + return fmt.Sprintf("%v", value) + } } - -func newStringer(str string) fmt.Stringer { - var impl stringerImpl = stringerImpl(str) - return &impl -} - -func newStringerFromRune(rn rune) fmt.Stringer { - return newStringer(string(rn)) -} - -var ( - spaceStringer = newStringer(" ") -) diff --git a/write_string.go b/write_string.go index 9df495c..522dc79 100644 --- a/write_string.go +++ b/write_string.go @@ -1,19 +1,18 @@ package buf2d import ( - "fmt" "strings" ) // WriteString writes a whole string to the buffer at position (x,y) // no word wrap is applied at all. If the string does not fit, it will be truncated -func (b *Buffer) WriteString(str string, x, y int) { +func WriteString(b *Buffer[rune], str string, x, y int) { dx := x for _, r := range str { if dx >= b.width { return } - b.Set(dx, y, newStringerFromRune(r)) + b.Set(dx, y, r) dx++ } } @@ -21,22 +20,21 @@ func (b *Buffer) WriteString(str string, x, y int) { // WriteMultiLineString writes a multi-line string to the buffer at position (x,y) // no word wrap is applied at all. If a line does not fit horizontally, it will be truncated // All lines which do not fit vertically will be truncated as well -func (b *Buffer) WriteMultiLineString(str string, x, y int) { +func WriteMultiLineString(b *Buffer[rune], str string, x, y int) { lines := strings.Split(str, "\n") for dy, line := range lines { if dy >= b.height { return } - - b.WriteString(line, x, y+dy) + WriteString(b, line, x, y+dy) } } // Fill fills the whole buffer with c -func (b *Buffer) Fill(c fmt.Stringer) { +func (b *Buffer[T]) Fill(value T) { for _, col := range b.data { for ri := range col { - col[ri] = c + col[ri] = value } } }