diff --git a/buffer.go b/buffer.go new file mode 100644 index 0000000..352a9e8 --- /dev/null +++ b/buffer.go @@ -0,0 +1,119 @@ +package buf2d + +import ( + "fmt" + "strings" +) + +// Buffer is a 2-dimensional rune buffer +type Buffer struct { + data [][]fmt.Stringer + width int + height int + parent *Buffer +} + +// NewBuffer makes a new buffer with the given dimensions +func NewBuffer(width, height int) *Buffer { + b := make([][]fmt.Stringer, height) + for y := range b { + b[y] = make([]fmt.Stringer, width) + for x := range b[y] { + b[y][x] = spaceStringer + } + } + + return &Buffer{ + data: b, + width: width, + height: height, + parent: nil, + } +} + +func (b *Buffer) x(x int) int { + return limit(x, 0, b.width-1) +} + +func (b *Buffer) 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 +} + +// 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 { + return b.data[y][x] +} + +// Size returns width and height of b +func (b *Buffer) Size() (w, h int) { + return b.width, b.height +} + +// Width returns the width of b +func (b *Buffer) Width() int { + return b.width +} + +// Height returns the height of b +func (b *Buffer) 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)) { + for y, col := range b.data { + for x, v := range col { + f(x, y, v) + } + } +} + +func (b *Buffer) String() string { + s := new(strings.Builder) + for ci, col := range b.data { + for _, v := range col { + s.WriteString(fmt.Sprintf("%v", 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) Sub(x, y, w, h int) *Buffer { + // sanitize inputs + x = limit(x, 0, b.width-1) + y = limit(y, 0, b.height-1) + w = limit(w, 1, b.width-x) + h = limit(h, 1, b.height-y) + + // make slice references + data := make([][]fmt.Stringer, 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, + } +} diff --git a/buffer_test.go b/buffer_test.go new file mode 100644 index 0000000..f1e96aa --- /dev/null +++ b/buffer_test.go @@ -0,0 +1,19 @@ +package buf2d + +import ( + "fmt" + "strings" + "testing" +) + +func TestSub(t *testing.T) { + 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) + + fmt.Println(b) + fmt.Println(strings.Repeat("-", 20)) + fmt.Println(s) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42c54d9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.tordarus.net/tordarus/buf2d + +go 1.15 diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..7473b6c --- /dev/null +++ b/utils.go @@ -0,0 +1,40 @@ +package buf2d + +import "fmt" + +func limit(v, min, max int) int { + return getmax(getmin(v, max), min) +} + +func getmax(x, y int) int { + if x > y { + return x + } + return y +} + +func getmin(x, y int) int { + if x < y { + return x + } + return y +} + +type stringerImpl string + +func (s *stringerImpl) String() string { + return string(*s) +} + +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 new file mode 100644 index 0000000..9df495c --- /dev/null +++ b/write_string.go @@ -0,0 +1,42 @@ +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) { + dx := x + for _, r := range str { + if dx >= b.width { + return + } + b.Set(dx, y, newStringerFromRune(r)) + dx++ + } +} + +// 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) { + lines := strings.Split(str, "\n") + for dy, line := range lines { + if dy >= b.height { + return + } + + b.WriteString(line, x, y+dy) + } +} + +// Fill fills the whole buffer with c +func (b *Buffer) Fill(c fmt.Stringer) { + for _, col := range b.data { + for ri := range col { + col[ri] = c + } + } +}