Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
95
buffer.go
95
buffer.go
@ -1,101 +1,76 @@
|
|||||||
package buf2d
|
package buf2d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buffer is a 2-dimensional buffer
|
// Buffer is a 2-dimensional rune buffer
|
||||||
type Buffer[T any] struct {
|
type Buffer struct {
|
||||||
data [][]T
|
data [][]fmt.Stringer
|
||||||
x, y int
|
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
parent *Buffer[T]
|
parent *Buffer
|
||||||
StringFunc func(T) string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuffer makes a new buffer with the given dimensions
|
// NewBuffer makes a new buffer with the given dimensions
|
||||||
func NewBuffer[T any](width, height int, defaultValue T) *Buffer[T] {
|
func NewBuffer(width, height int) *Buffer {
|
||||||
b := make([][]T, height)
|
b := make([][]fmt.Stringer, height)
|
||||||
for y := range b {
|
for y := range b {
|
||||||
b[y] = make([]T, width)
|
b[y] = make([]fmt.Stringer, width)
|
||||||
for x := range b[y] {
|
for x := range b[y] {
|
||||||
b[y][x] = defaultValue
|
b[y][x] = spaceStringer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Buffer[T]{
|
return &Buffer{
|
||||||
x: 0, y: 0,
|
|
||||||
data: b,
|
data: b,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
parent: nil,
|
parent: nil,
|
||||||
StringFunc: MakeDefaultStringFunc[T](),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer[T]) limX(x int) int {
|
func (b *Buffer) x(x int) int {
|
||||||
return limit(x, 0, b.width-1)
|
return limit(x, 0, b.width-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer[T]) limY(y int) int {
|
func (b *Buffer) y(y int) int {
|
||||||
return limit(y, 0, b.height-1)
|
return limit(y, 0, b.height-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the value at position (x,y) to c
|
// Set sets the fmt.Stringer at position (x,y) to c
|
||||||
func (b *Buffer[T]) Set(x, y int, v T) {
|
func (b *Buffer) Set(x, y int, c fmt.Stringer) {
|
||||||
if b.width > 0 && b.height > 0 {
|
b.data[b.y(y)][b.x(x)] = c
|
||||||
b.data[b.limY(y)][b.limX(x)] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value at position (x,y)
|
// SetRune sets the fmt.Stringer at position (x,y) to rn
|
||||||
func (b *Buffer[T]) Get(x, y int) T {
|
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]
|
return b.data[y][x]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns width and height of b
|
// Size returns width and height of b
|
||||||
func (b *Buffer[T]) Size() (w, h int) {
|
func (b *Buffer) Size() (w, h int) {
|
||||||
return b.width, b.height
|
return b.width, b.height
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width of b
|
// Width returns the width of b
|
||||||
func (b *Buffer[T]) Width() int {
|
func (b *Buffer) Width() int {
|
||||||
return b.width
|
return b.width
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height of b
|
// Height returns the height of b
|
||||||
func (b *Buffer[T]) Height() int {
|
func (b *Buffer) Height() int {
|
||||||
return b.height
|
return b.height
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset returns the offset of b relative to its parent buffer.
|
// ForEach calls f for every rune in this buffer
|
||||||
// Offset returns zeros if b has no parent
|
func (b *Buffer) ForEach(f func(x, y int, c fmt.Stringer)) {
|
||||||
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 y, col := range b.data {
|
||||||
for x, v := range col {
|
for x, v := range col {
|
||||||
f(x, y, v)
|
f(x, y, v)
|
||||||
@ -103,11 +78,11 @@ func (b *Buffer[T]) ForEach(f func(x, y int, v T)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer[T]) String() string {
|
func (b *Buffer) String() string {
|
||||||
s := new(strings.Builder)
|
s := new(strings.Builder)
|
||||||
for ci, col := range b.data {
|
for ci, col := range b.data {
|
||||||
for _, v := range col {
|
for _, v := range col {
|
||||||
s.WriteString(b.StringFunc(v))
|
s.WriteString(fmt.Sprintf("%v", v))
|
||||||
}
|
}
|
||||||
if ci != len(b.data)-1 {
|
if ci != len(b.data)-1 {
|
||||||
s.WriteRune('\n')
|
s.WriteRune('\n')
|
||||||
@ -120,27 +95,25 @@ func (b *Buffer[T]) String() string {
|
|||||||
// Modifying the main buffer or the sub buffer will modify the other one as well
|
// Modifying the main buffer or the sub buffer will modify the other one as well
|
||||||
// This method can be used recursively
|
// This method can be used recursively
|
||||||
// If the given dimensions don't fit in the parent buffer, it will be truncated
|
// 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] {
|
func (b *Buffer) Sub(x, y, w, h int) *Buffer {
|
||||||
// sanitize inputs
|
// sanitize inputs
|
||||||
x = limit(x, 0, b.width-1)
|
x = limit(x, 0, b.width-1)
|
||||||
y = limit(y, 0, b.height-1)
|
y = limit(y, 0, b.height-1)
|
||||||
w = limit(w, 0, b.width-x)
|
w = limit(w, 1, b.width-x)
|
||||||
h = limit(h, 0, b.height-y)
|
h = limit(h, 1, b.height-y)
|
||||||
|
|
||||||
// make slice references
|
// make slice references
|
||||||
data := make([][]T, h)
|
data := make([][]fmt.Stringer, h)
|
||||||
for dy := 0; dy < h; dy++ {
|
for dy := 0; dy < h; dy++ {
|
||||||
col := b.data[y+dy]
|
col := b.data[y+dy]
|
||||||
data[dy] = col[x : x+w]
|
data[dy] = col[x : x+w]
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer
|
// make buffer
|
||||||
return &Buffer[T]{
|
return &Buffer{
|
||||||
x: x, y: y,
|
|
||||||
data: data,
|
data: data,
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
parent: b,
|
parent: b,
|
||||||
StringFunc: b.StringFunc,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSub(t *testing.T) {
|
func TestSub(t *testing.T) {
|
||||||
b := NewBuffer(10, 10, ' ')
|
b := NewBuffer(10, 10)
|
||||||
s := b.Sub(1, 1, b.Width()-1, b.Height()-1)
|
s := b.Sub(1, 1, b.Width()-1, b.Height()-1)
|
||||||
b.Set(5, 1, 'a')
|
b.SetRune(5, 1, 'a')
|
||||||
s.Set(5, 5, 'b')
|
s.SetRune(5, 5, 'b')
|
||||||
WriteString(b, "Hello world", 1, 2)
|
b.WriteString("Hello world", 1, 2)
|
||||||
|
|
||||||
fmt.Println(b)
|
fmt.Println(b)
|
||||||
fmt.Println(strings.Repeat("-", 10))
|
fmt.Println(strings.Repeat("-", 20))
|
||||||
fmt.Println(s)
|
fmt.Println(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSet(t *testing.T) {
|
|
||||||
b := NewBuffer(0, 0, ' ')
|
|
||||||
b.Set(0, 0, 'a')
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOffset(t *testing.T) {
|
|
||||||
b := NewBuffer(10, 10, ' ')
|
|
||||||
b = b.Sub(3, 3, 3, 3)
|
|
||||||
fmt.Println(b.OffsetX(), b.OffsetY())
|
|
||||||
}
|
|
||||||
|
8
go.mod
8
go.mod
@ -1,7 +1,3 @@
|
|||||||
module git.tordarus.net/tordarus/buf2d
|
module git.tordarus.net/Tordarus/buf2d
|
||||||
|
|
||||||
go 1.18
|
go 1.15
|
||||||
|
|
||||||
require github.com/mattn/go-runewidth v0.0.14
|
|
||||||
|
|
||||||
require github.com/rivo/uniseg v0.2.0 // indirect
|
|
||||||
|
4
go.sum
4
go.sum
@ -1,4 +0,0 @@
|
|||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
34
utils.go
34
utils.go
@ -1,9 +1,6 @@
|
|||||||
package buf2d
|
package buf2d
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func limit(v, min, max int) int {
|
func limit(v, min, max int) int {
|
||||||
return getmax(getmin(v, max), min)
|
return getmax(getmin(v, max), min)
|
||||||
@ -23,18 +20,21 @@ func getmin(x, y int) int {
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeDefaultStringFunc[T any]() func(T) string {
|
type stringerImpl 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(value T) string {
|
func (s *stringerImpl) String() string {
|
||||||
return fmt.Sprintf("%v", value)
|
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(" ")
|
||||||
|
)
|
||||||
|
@ -1,44 +1,42 @@
|
|||||||
package buf2d
|
package buf2d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteString writes a whole string to the buffer at position (x,y).
|
// 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.
|
// no word wrap is applied at all. If the string does not fit, it will be truncated
|
||||||
// It returns the amount of runes in str
|
func (b *Buffer) WriteString(str string, x, y int) {
|
||||||
func WriteString(b *Buffer[rune], str string, x, y int) (width int) {
|
|
||||||
dx := x
|
dx := x
|
||||||
for _, r := range str {
|
for _, r := range str {
|
||||||
if dx >= b.Width() {
|
if dx >= b.width {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.Set(dx, y, r)
|
b.Set(dx, y, newStringerFromRune(r))
|
||||||
dx += runewidth.RuneWidth(r)
|
dx++
|
||||||
}
|
}
|
||||||
return dx - x
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMultiLineString writes a multi-line string to the buffer at position (x,y)
|
// 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
|
// 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
|
// All lines which do not fit vertically will be truncated as well
|
||||||
func WriteMultiLineString(b *Buffer[rune], str string, x, y int) {
|
func (b *Buffer) WriteMultiLineString(str string, x, y int) {
|
||||||
lines := strings.Split(str, "\n")
|
lines := strings.Split(str, "\n")
|
||||||
for dy, line := range lines {
|
for dy, line := range lines {
|
||||||
if dy >= b.height {
|
if dy >= b.height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteString(b, line, x, y+dy)
|
|
||||||
|
b.WriteString(line, x, y+dy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill fills the whole buffer with c
|
// Fill fills the whole buffer with c
|
||||||
func (b *Buffer[T]) Fill(value T) {
|
func (b *Buffer) Fill(c fmt.Stringer) {
|
||||||
for _, col := range b.data {
|
for _, col := range b.data {
|
||||||
for ri := range col {
|
for ri := range col {
|
||||||
col[ri] = value
|
col[ri] = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user