diff --git a/gim/box.go b/gim/box.go index 4c69257..f5f0125 100644 --- a/gim/box.go +++ b/gim/box.go @@ -9,24 +9,6 @@ import ( "github.com/mediocregopher/ginger/gim/terminal" ) -const ( - boxBorderHoriz = iota - boxBorderVert - boxBorderTL - boxBorderTR - boxBorderBL - boxBorderBR -) - -var boxDefault = []string{ - "─", - "│", - "┌", - "┐", - "└", - "┘", -} - type box struct { topLeft geo.XY flowDir geo.XY @@ -58,6 +40,7 @@ func (b box) bodyLines() []string { return lines } +// TODO this is utterly broken, the terminal.Buffer should be used for this func (b box) bodySize() geo.XY { var size geo.XY for _, line := range b.bodyLines() { @@ -102,47 +85,14 @@ func (b box) bodyRect() geo.Rect { return geo.Rect{Size: b.bodySize()}.Centered(center, rounder) } -func (b box) draw(term *terminal.Terminal) { - chars := boxDefault +func (b box) draw(buf *terminal.Buffer) { + bodyBuf := terminal.NewBuffer() + bodyBuf.WriteString(b.body) + bodyBufRect := geo.Rect{Size: bodyBuf.Size()} + rect := b.rect() - pos := rect.TopLeft - w, h := rect.Size[0], rect.Size[1] + buf.DrawRect(rect, terminal.SingleLine) - // draw top line - term.MoveCursorTo(pos) - term.Printf(chars[boxBorderTL]) - for i := 0; i < w-2; i++ { - term.Printf(chars[boxBorderHoriz]) - } - term.Printf(chars[boxBorderTR]) - pos[1]++ - - // draw vertical lines - for i := 0; i < h-2; i++ { - term.MoveCursorTo(pos) - term.Printf(chars[boxBorderVert]) - if b.transparent { - term.MoveCursorTo(pos.Add(geo.XY{w, 0})) - } else { - term.Printf(strings.Repeat(" ", w-2)) - } - term.Printf(chars[boxBorderVert]) - pos[1]++ - } - - // draw bottom line - term.MoveCursorTo(pos) - term.Printf(chars[boxBorderBL]) - for i := 0; i < w-2; i++ { - term.Printf(chars[boxBorderHoriz]) - } - term.Printf(chars[boxBorderBR]) - - // write out inner lines - pos = b.bodyRect().TopLeft - for _, line := range b.bodyLines() { - term.MoveCursorTo(pos) - term.Printf(line) - pos[1]++ - } + center := rect.Center(rounder) + buf.DrawBuffer(bodyBufRect.Centered(center, rounder).TopLeft, bodyBuf) } diff --git a/gim/line.go b/gim/line.go index 9538688..272a90f 100644 --- a/gim/line.go +++ b/gim/line.go @@ -5,38 +5,20 @@ import ( "github.com/mediocregopher/ginger/gim/terminal" ) -var lineSegments = func() map[[2]geo.XY]string { - m := map[[2]geo.XY]string{ - {geo.Left, geo.Right}: "─", - {geo.Down, geo.Up}: "│", - {geo.Right, geo.Down}: "┌", - {geo.Left, geo.Down}: "┐", - {geo.Right, geo.Up}: "└", - {geo.Left, geo.Up}: "┘", - } - - // the inverse segments use the same characters - for seg, str := range m { - seg[0], seg[1] = seg[1], seg[0] - m[seg] = str - } - return m -}() - -var edgeSegments = map[geo.XY]string{ - geo.Up: "┴", - geo.Down: "┬", - geo.Left: "┤", - geo.Right: "├", +var edgeSegments = map[geo.XY]rune{ + geo.Up: '┴', + geo.Down: '┬', + geo.Left: '┤', + geo.Right: '├', } // actual unicode arrows were fucking up my terminal, and they didn't even // connect properly with the line segments anyway -var arrows = map[geo.XY]string{ - geo.Up: "^", - geo.Down: "v", - geo.Left: "<", - geo.Right: ">", +var arrows = map[geo.XY]rune{ + geo.Up: '^', + geo.Down: 'v', + geo.Left: '<', + geo.Right: '>', } type line struct { @@ -54,7 +36,7 @@ func secondaryDir(flowDir, start, end geo.XY) geo.XY { return end.Sub(start).Mul(perpDir.Abs()).Unit() } -func (l line) draw(term *terminal.Terminal, flowDir, secFlowDir geo.XY) { +func (l line) draw(buf *terminal.Buffer, flowDir, secFlowDir geo.XY) { from, to := *(l.from), *(l.to) start := from.rect().Edge(flowDir, secFlowDir)[0].Add(secFlowDir.Scale(l.fromI*2 + 1)) @@ -86,28 +68,24 @@ func (l line) draw(term *terminal.Terminal, flowDir, secFlowDir geo.XY) { // draw each point for i, pt := range pts { - var str string + var r rune switch { case i == 0: - str = edgeSegments[flowDir] + r = edgeSegments[flowDir] case i == len(pts)-1: - str = arrows[flowDir] + r = arrows[flowDir] default: prev, next := pts[i-1], pts[i+1] - seg := [2]geo.XY{ - prev.Sub(pt), - next.Sub(pt), - } - str = lineSegments[seg] + r = terminal.SingleLine.Segment(prev.Sub(pt), next.Sub(pt)) } - term.MoveCursorTo(pt) - term.Printf(str) + buf.SetPos(pt) + buf.WriteRune(r) } // draw the body if l.body != "" { bodyPos := mid.Add(geo.Left.Scale(len(l.body) / 2)) - term.MoveCursorTo(bodyPos) - term.Printf(l.body) + buf.SetPos(bodyPos) + buf.WriteString(l.body) } } diff --git a/gim/main.go b/gim/main.go index 77353c2..73437ea 100644 --- a/gim/main.go +++ b/gim/main.go @@ -80,8 +80,7 @@ func mkGraph() (*gg.Graph, gg.Value) { func main() { rand.Seed(time.Now().UnixNano()) term := terminal.New() - //term.Reset() - //term.HideCursor() + wSize := term.WindowSize() g, start := mkGraph() v := view{ @@ -89,13 +88,11 @@ func main() { primFlowDir: geo.Right, secFlowDir: geo.Down, start: start, - center: geo.Zero.Midpoint(term.WindowSize(), rounder), + center: geo.Zero.Midpoint(wSize, rounder), } - //for range time.Tick(frameperiod) { - term.Reset() + term.Clear() v.draw(term) - term.Flush() - //} - time.Sleep(1 * time.Hour) + term.SetPos(wSize.Add(geo.XY{0, -1})) + term.Draw() } diff --git a/gim/terminal/buffer.go b/gim/terminal/buffer.go new file mode 100644 index 0000000..c23eb25 --- /dev/null +++ b/gim/terminal/buffer.go @@ -0,0 +1,210 @@ +package terminal + +import ( + "fmt" + "strconv" + "unicode" + + "github.com/mediocregopher/ginger/gim/geo" +) + +// Reset all custom styles +const ansiReset = "\033[0m" + +// Color describes the foreground or background color of text +type Color int + +// Available Color values +const ( + // whatever the terminal's default color scheme is + Default = iota + + Black + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +type bufStyle struct { + fgColor Color + bgColor Color +} + +// returns foreground and background ansi codes +func (bf bufStyle) ansi() (string, string) { + var fg, bg string + if bf.fgColor != Default { + fg = "\033[0;3" + strconv.Itoa(int(bf.fgColor)-1) + "m" + } + if bf.bgColor != Default { + bg = "\033[0;4" + strconv.Itoa(int(bf.bgColor)-1) + "m" + } + return fg, bg +} + +// returns the ansi sequence which would modify the style to the given one +func (bf bufStyle) diffTo(bf2 bufStyle) string { + // this implementation is naive, but whatever + if bf == bf2 { + return "" + } + + fg, bg := bf2.ansi() + if (bf == bufStyle{}) { + return fg + bg + } + return ansiReset + fg + bg +} + +type bufPoint struct { + r rune + bufStyle +} + +// Buffer describes an infinitely sized terminal buffer to which anything may be +// drawn, and which will efficiently generate strings representing the drawn +// text. +type Buffer struct { + currStyle bufStyle + currPos geo.XY + m *mat + max geo.XY +} + +// NewBuffer initializes and returns a new empty buffer. The proper way to clear +// a buffer is to toss the old one and generate a new one. +func NewBuffer() *Buffer { + return &Buffer{ + m: newMat(), + max: geo.XY{-1, -1}, + } +} + +// Copy creates a new identical instance of this Buffer and returns it. +func (b *Buffer) Copy() *Buffer { + b2 := NewBuffer() + b.m.iter(func(x, y int, v interface{}) bool { + b2.setRune(geo.XY{x, y}, v.(bufPoint)) + return true + }) + b2.currStyle = b.currStyle + b2.currPos = b.currPos + return b2 +} + +func (b *Buffer) setRune(at geo.XY, p bufPoint) { + b.m.set(at[0], at[1], p) + b.max = b.max.Max(at) +} + +// WriteRune writes the given rune to the Buffer at whatever the current +// position is, with whatever the current styling is. +func (b *Buffer) WriteRune(r rune) { + if r == '\n' { + b.currPos[0], b.currPos[1] = 0, b.currPos[1]+1 + return + } else if r == '\r' { + b.currPos[0] = 0 + } else if !unicode.IsPrint(r) { + panic(fmt.Sprintf("character %q is not supported by terminal.Buffer", r)) + } + + b.setRune(b.currPos, bufPoint{ + r: r, + bufStyle: b.currStyle, + }) + b.currPos[0]++ +} + +// WriteString writes the given string to the Buffer at whatever the current +// position is, with whatever the current styling is. +func (b *Buffer) WriteString(s string) { + for _, r := range s { + b.WriteRune(r) + } +} + +// SetPos sets the cursor position in the Buffer, so Print operations will begin +// at that point. Remember that the origin is at point (0, 0). +func (b *Buffer) SetPos(xy geo.XY) { + b.currPos = xy +} + +// SetFGColor sets subsequent text's foreground color. +func (b *Buffer) SetFGColor(c Color) { + b.currStyle.fgColor = c +} + +// SetBGColor sets subsequent text's background color. +func (b *Buffer) SetBGColor(c Color) { + b.currStyle.bgColor = c +} + +// ResetStyle unsets all text styling options which have been set. +func (b *Buffer) ResetStyle() { + b.currStyle = bufStyle{} +} + +// String renders and returns a string which, when printed to a terminal, will +// print the Buffer's contents at the terminal's current cursor position. +func (b *Buffer) String() string { + s := ansiReset // always start with a reset + var style bufStyle + var pos geo.XY + move := func(to geo.XY) { + diff := to.Sub(pos) + if diff[0] > 0 { + s += "\033[" + strconv.Itoa(diff[0]) + "C" + } else if diff[0] < 0 { + s += "\033[" + strconv.Itoa(-diff[0]) + "D" + } + if diff[1] > 0 { + s += "\033[" + strconv.Itoa(diff[1]) + "B" + } else if diff[1] < 0 { + s += "\033[" + strconv.Itoa(-diff[1]) + "A" + } + pos = to + } + + b.m.iter(func(x, y int, v interface{}) bool { + p := v.(bufPoint) + move(geo.XY{x, y}) + s += style.diffTo(p.bufStyle) + style = p.bufStyle + s += string(p.r) + pos[0]++ + return true + }) + return s +} + +// DrawBuffer copies the given Buffer onto this one, with the given's top-left +// corner being at the given position. The given buffer may be the same as this +// one. +// +// Calling this method does not affect this Buffer's current cursor position or +// style. +func (b *Buffer) DrawBuffer(at geo.XY, b2 *Buffer) { + if b == b2 { + b2 = b2.Copy() + } + b2.m.iter(func(x, y int, v interface{}) bool { + x += at[0] + y += at[1] + if x < 0 || y < 0 { + return true + } + b.setRune(geo.XY{x, y}, v.(bufPoint)) + return true + }) +} + +// Size returns the dimensions of the Buffer's current area which has been +// written to. +func (b *Buffer) Size() geo.XY { + return b.max.Add(geo.XY{1, 1}) +} diff --git a/gim/terminal/example/main.go b/gim/terminal/example/main.go new file mode 100644 index 0000000..1dcd905 --- /dev/null +++ b/gim/terminal/example/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "log" + "time" + + "github.com/mediocregopher/ginger/gim/geo" + "github.com/mediocregopher/ginger/gim/terminal" +) + +func main() { + b := terminal.NewBuffer() + b.WriteString("this is fun") + + b.SetFGColor(terminal.Blue) + b.SetBGColor(terminal.Green) + b.SetPos(geo.XY{18, 0}) + b.WriteString("blue and green") + + b.ResetStyle() + b.SetFGColor(terminal.Red) + b.SetPos(geo.XY{3, 3}) + b.WriteString("red!!!") + + b.ResetStyle() + b.SetFGColor(terminal.Blue) + b.SetPos(geo.XY{20, 0}) + b.WriteString("boo") + + bcp := b.Copy() + b.DrawBuffer(geo.XY{2, 2}, bcp) + b.DrawBuffer(geo.XY{-1, 1}, bcp) + + brect := terminal.NewBuffer() + brect.DrawRect(geo.Rect{Size: b.Size().Add(geo.XY{2, 2})}, terminal.SingleLine) + log.Printf("b.Size:%v", b.Size()) + brect.DrawBuffer(geo.XY{1, 1}, b) + + t := terminal.New() + p := geo.XY{0, 0} + dirH, dirV := geo.Right, geo.Down + wsize := t.WindowSize() + for range time.Tick(time.Second / 15) { + t.Clear() + t.WriteBuffer(p, brect) + t.Draw() + + brectSize := brect.Size() + p = p.Add(dirH).Add(dirV) + if p[0] < 0 || p[0]+brectSize[0] > wsize[0] { + dirH = dirH.Scale(-1) + p = p.Add(dirH.Scale(2)) + } + if p[1] < 0 || p[1]+brectSize[1] > wsize[1] { + dirV = dirV.Scale(-1) + p = p.Add(dirV.Scale(2)) + } + } +} diff --git a/gim/terminal/mat.go b/gim/terminal/mat.go new file mode 100644 index 0000000..087c5ab --- /dev/null +++ b/gim/terminal/mat.go @@ -0,0 +1,117 @@ +package terminal + +import ( + "container/list" +) + +type matEl struct { + x int + v interface{} +} + +type matRow struct { + y int + l *list.List +} + +// a 2-d sparse matrix +type mat struct { + rows *list.List + + currY int + currRowEl *list.Element + currEl *list.Element +} + +func newMat() *mat { + return &mat{ + rows: list.New(), + } +} + +func (m *mat) getRow(y int) *list.List { + m.currY = y // this will end up being true no matter what + if m.currRowEl == nil { // first call + l := list.New() + m.currRowEl = m.rows.PushFront(matRow{y: y, l: l}) + return l + + } else if m.currRowEl.Value.(matRow).y > y { + m.currRowEl = m.rows.Front() + } + + for { + currRow := m.currRowEl.Value.(matRow) + switch { + case currRow.y == y: + return currRow.l + case currRow.y < y: + if m.currRowEl = m.currRowEl.Next(); m.currRowEl == nil { + l := list.New() + m.currRowEl = m.rows.PushBack(matRow{y: y, l: l}) + return l + } + default: // currRow.y > y + l := list.New() + m.currRowEl = m.rows.InsertBefore(matRow{y: y, l: l}, m.currRowEl) + return l + } + } +} + +func (m *mat) getEl(x, y int) *matEl { + var rowL *list.List + if m.currRowEl == nil || m.currY != y { + rowL = m.getRow(y) + m.currEl = rowL.Front() + } else { + rowL = m.currRowEl.Value.(matRow).l + } + + if m.currEl == nil || m.currEl.Value.(*matEl).x > x { + if m.currEl = rowL.Front(); m.currEl == nil { + // row is empty + mel := &matEl{x: x} + m.currEl = rowL.PushFront(mel) + return mel + } + } + + for { + currEl := m.currEl.Value.(*matEl) + switch { + case currEl.x == x: + return currEl + case currEl.x < x: + if m.currEl = m.currEl.Next(); m.currEl == nil { + mel := &matEl{x: x} + m.currEl = rowL.PushBack(mel) + return mel + } + default: // currEl.x > x + mel := &matEl{x: x} + m.currEl = rowL.InsertBefore(mel, m.currEl) + return mel + } + } +} + +func (m *mat) get(x, y int) interface{} { + return m.getEl(x, y).v +} + +func (m *mat) set(x, y int, v interface{}) { + m.getEl(x, y).v = v +} + +func (m *mat) iter(f func(x, y int, v interface{}) bool) { + for rowEl := m.rows.Front(); rowEl != nil; rowEl = rowEl.Next() { + row := rowEl.Value.(matRow) + for el := row.l.Front(); el != nil; el = el.Next() { + mel := el.Value.(*matEl) + if !f(mel.x, row.y, mel.v) { + return + } + } + } +} diff --git a/gim/terminal/mat_test.go b/gim/terminal/mat_test.go new file mode 100644 index 0000000..a856c0e --- /dev/null +++ b/gim/terminal/mat_test.go @@ -0,0 +1,59 @@ +package terminal + +import ( + "fmt" + "math/rand" + "strings" + . "testing" + "time" +) + +func TestMat(t *T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + type xy struct { + x, y int + } + + type action struct { + xy + set int + } + + run := func(aa []action) { + aaStr := func(i int) string { + s := fmt.Sprintf("%#v", aa[:i+1]) + return strings.Replace(s, "terminal.", "", -1) + } + + m := newMat() + mm := map[xy]int{} + for i, a := range aa { + if a.set > 0 { + mm[a.xy] = a.set + m.set(a.xy.x, a.xy.y, a.set) + continue + } + + expI, expOk := mm[a.xy] + gotI, gotOk := m.get(a.xy.x, a.xy.y).(int) + if expOk != gotOk { + t.Fatalf("get failed: expOk:%v gotOk:%v actions:%#v", expOk, gotOk, aaStr(i)) + } else if expI != gotI { + t.Fatalf("get failed: expI:%v gotI:%v actions:%#v", expI, gotI, aaStr(i)) + } + } + } + + for i := 0; i < 10000; i++ { + var actions []action + for j := r.Intn(1000); j > 0; j-- { + a := action{xy: xy{x: r.Intn(5), y: r.Intn(5)}} + if r.Intn(3) == 0 { + a.set = r.Intn(10000) + 1 + } + actions = append(actions, a) + } + run(actions) + } +} diff --git a/gim/terminal/shape.go b/gim/terminal/shape.go new file mode 100644 index 0000000..f483841 --- /dev/null +++ b/gim/terminal/shape.go @@ -0,0 +1,86 @@ +package terminal + +import ( + "fmt" + "strings" + + "github.com/mediocregopher/ginger/gim/geo" +) + +// SingleLine is a set of single-pixel-width lines. +var SingleLine = LineStyle{ + Horiz: '─', + Vert: '│', + TopLeft: '┌', + TopRight: '┐', + BottomLeft: '└', + BottomRight: '┘', +} + +// LineStyle defines a set of characters to use together when drawing lines and +// corners. +type LineStyle struct { + Horiz, Vert rune + // Corner characters, identified as corners of a rectangle + TopLeft, TopRight, BottomLeft, BottomRight rune +} + +// Segment takes two different directions (i.e. geo.Up/Down/Left/Right) and +// returns the line character which points in both of those directions. +// +// For example, SingleLine.Segment(geo.Up, geo.Left) returns '┘'. +func (ls LineStyle) Segment(a, b geo.XY) rune { + inner := func(a, b geo.XY) rune { + type c struct{ a, b geo.XY } + switch (c{a, b}) { + case c{geo.Up, geo.Down}: + return ls.Vert + case c{geo.Left, geo.Right}: + return ls.Horiz + case c{geo.Down, geo.Right}: + return ls.TopLeft + case c{geo.Down, geo.Left}: + return ls.TopRight + case c{geo.Up, geo.Right}: + return ls.BottomLeft + case c{geo.Up, geo.Left}: + return ls.BottomRight + default: + return 0 + } + } + if r := inner(a, b); r != 0 { + return r + } else if r = inner(b, a); r != 0 { + return r + } + panic(fmt.Sprintf("invalid LineStyle.Segment directions: %v, %v", a, b)) +} + +// DrawRect draws the given Rect to the Buffer with the given LineStyle. The +// Rect's TopLeft field is used for its position. +// +// If Rect's Size is not at least 2x2 this does nothing. +func (b *Buffer) DrawRect(r geo.Rect, ls LineStyle) { + if r.Size[0] < 2 || r.Size[1] < 2 { + return + } + horiz := strings.Repeat(string(ls.Horiz), r.Size[0]-2) + + b.SetPos(r.TopLeft) + b.WriteRune(ls.TopLeft) + b.WriteString(horiz) + b.WriteRune(ls.TopRight) + + for i := 0; i < r.Size[1]-2; i++ { + b.SetPos(r.TopLeft.Add(geo.XY{0, i + 1})) + b.WriteRune(ls.Vert) + b.SetPos(r.TopLeft.Add(geo.XY{r.Size[0] - 1, i + 1})) + b.WriteRune(ls.Vert) + } + + b.SetPos(r.TopLeft.Add(geo.XY{0, r.Size[1] - 1})) + b.WriteRune(ls.BottomLeft) + b.WriteString(horiz) + b.WriteRune(ls.BottomRight) +} diff --git a/gim/terminal/terminal.go b/gim/terminal/terminal.go index dfa81d5..982f004 100644 --- a/gim/terminal/terminal.go +++ b/gim/terminal/terminal.go @@ -8,7 +8,6 @@ import ( "io" "os" "syscall" - "unicode/utf8" "unsafe" "github.com/mediocregopher/ginger/gim/geo" @@ -34,7 +33,6 @@ import ( // type Terminal struct { buf *bytes.Buffer - pos geo.XY // When initialized this will be set to os.Stdout, but can be set to // anything @@ -70,17 +68,10 @@ func (t *Terminal) WindowSize() geo.XY { return geo.XY{int(sz.cols), int(sz.rows)} } -// MoveCursorTo moves the cursor to the given position -func (t *Terminal) MoveCursorTo(to geo.XY) { +// SetPos sets the terminal's actual cursor position to the given coordinates. +func (t *Terminal) SetPos(to geo.XY) { // actual terminal uses 1,1 as top-left, because 1-indexing is a great idea fmt.Fprintf(t.buf, "\033[%d;%dH", to[1]+1, to[0]+1) - t.pos = to -} - -// MoveCursor moves the cursor relative to its current position by the given -// vector -func (t *Terminal) MoveCursor(by geo.XY) { - t.MoveCursorTo(t.pos.Add(by)) } // HideCursor causes the cursor to not actually be shown @@ -93,64 +84,25 @@ func (t *Terminal) ShowCursor() { fmt.Fprintf(t.buf, "\033[?25h") } -// Reset completely clears all drawn characters on the screen and returns the -// cursor to the origin -func (t *Terminal) Reset() { +// Clear completely clears all drawn characters on the screen and returns the +// cursor to the origin. This implicitly calls Draw. +func (t *Terminal) Clear() { + t.buf.Reset() fmt.Fprintf(t.buf, "\033[2J") + t.Draw() } -// Printf prints the given formatted string to the terminal, updating the -// internal cursor position accordingly -func (t *Terminal) Printf(format string, args ...interface{}) { - str := fmt.Sprintf(format, args...) - t.buf.WriteString(str) - t.pos[0] += utf8.RuneCountInString(str) +// WriteBuffer writes the contents to the Buffer to the Terminal's buffer, +// starting at the given coordinate. +func (t *Terminal) WriteBuffer(at geo.XY, b *Buffer) { + t.SetPos(at) + t.buf.WriteString(b.String()) } -// Flush writes all buffered changes to the screen -func (t *Terminal) Flush() { +// Draw writes all buffered changes to the screen +func (t *Terminal) Draw() { if _, err := io.Copy(t.Out, t.buf); err != nil { panic(err) } -} - -// TODO deal with these - -const ( - // Reset all custom styles - ansiReset = "\033[0m" - - // Reset to default color - ansiResetColor = "\033[32m" - - // Return curor to start of line and clean it - ansiResetLine = "\r\033[K" -) - -// List of possible colors -const ( - black = iota - red - green - yellow - blue - magenta - cyan - white -) - -func getFgColor(code int) string { - return fmt.Sprintf("\033[3%dm", code) -} - -func getBgColor(code int) string { - return fmt.Sprintf("\033[4%dm", code) -} - -func fgColor(str string, color int) string { - return fmt.Sprintf("%s%s%s", getFgColor(color), str, ansiReset) -} - -func bgColor(str string, color int) string { - return fmt.Sprintf("%s%s%s", getBgColor(color), str, ansiReset) + t.buf.Reset() } diff --git a/gim/view.go b/gim/view.go index 2e66269..944e016 100644 --- a/gim/view.go +++ b/gim/view.go @@ -104,7 +104,7 @@ type view struct { g *gg.Graph primFlowDir, secFlowDir geo.XY start gg.Value - center geo.XY + center geo.XY // TODO this shouldnt be needed } func (view *view) draw(term *terminal.Terminal) { @@ -190,10 +190,12 @@ func (view *view) draw(term *terminal.Terminal) { centerBoxes(boxes, view.center) // actually draw the boxes and lines + buf := terminal.NewBuffer() for _, b := range boxes { - b.draw(term) + b.draw(buf) } for _, line := range lines { - line.draw(term, view.primFlowDir, view.secFlowDir) + line.draw(buf, view.primFlowDir, view.secFlowDir) } + term.WriteBuffer(geo.Zero, buf) }