aboutsummaryrefslogtreecommitdiffstats
path: root/internal/render
diff options
context:
space:
mode:
Diffstat (limited to 'internal/render')
-rw-r--r--internal/render/render.go74
1 files changed, 74 insertions, 0 deletions
diff --git a/internal/render/render.go b/internal/render/render.go
new file mode 100644
index 0000000..877e2bd
--- /dev/null
+++ b/internal/render/render.go
@@ -0,0 +1,74 @@
+// Package render renders to the terminal using ANSI escape sequences.
+package render
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+)
+
+// Renderer updates a terminal UI. Typical usage looks like
+//
+// r := render.New()
+// // Game loop
+// for {
+// // Update state
+//
+// r.Clear()
+// fmt.Fprintf(r, "Render UI by writing to r using io.Writer")
+// r.Flush()
+// }
+type Renderer struct {
+ w bufio.Writer
+ prevLines int
+}
+
+// New creates a new Renderer
+func New() *Renderer {
+ r := &Renderer{}
+ r.w.Reset(os.Stderr)
+ return r
+}
+
+// Clear clears the screen before rendering a new frame.
+func (r *Renderer) Clear() {
+ if r.prevLines > 0 {
+ fmt.Fprintf(&r.w, "\033[%dA", r.prevLines)
+ }
+ r.prevLines = 0
+ r.w.WriteString("\r")
+}
+
+// Write implements io.Writer.
+func (r *Renderer) Write(buf []byte) (int, error) {
+ totalBytes := 0
+ for len(buf) > 0 {
+ i := bytes.IndexByte(buf, '\n')
+ if i < 0 {
+ n, err := r.w.Write(buf)
+ totalBytes += n
+ return totalBytes, err
+ }
+ line := buf[:i]
+ buf = buf[i+1:]
+ n, err := r.w.Write(line)
+ totalBytes += n
+ if err != nil {
+ return totalBytes, err
+ }
+ if _, err := r.w.WriteString("\033[K\n"); err != nil {
+ return totalBytes, err
+ }
+ totalBytes++
+ r.prevLines++
+ }
+ return totalBytes, nil
+}
+
+// Flush flushes the internal buffer to stdout. Flush should be called at the
+// end of every frame.
+func (r *Renderer) Flush() {
+ r.w.WriteString("\033[J")
+ r.w.Flush()
+}