summaryrefslogtreecommitdiff
path: root/internal/render/instructions.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/render/instructions.go')
-rw-r--r--internal/render/instructions.go218
1 files changed, 218 insertions, 0 deletions
diff --git a/internal/render/instructions.go b/internal/render/instructions.go
new file mode 100644
index 0000000..685f3f8
--- /dev/null
+++ b/internal/render/instructions.go
@@ -0,0 +1,218 @@
+package render
+
+import (
+ "fmt"
+ "io"
+ "strings"
+)
+
+type (
+ trimDir uint8
+ overlapType uint8
+)
+
+const (
+ ToLeft trimDir = iota
+ ToRight
+ None overlapType = iota
+ Covers
+ Within
+ Left
+ Right
+)
+
+type Instruction interface {
+ // Write writes the instruction to the given io.Writer.
+ Write(io.Writer)
+ // Pos returns the x,y position of the instruction.
+ Pos() (int, int)
+ // Len returns the length of the instructions content.
+ Len() int
+ // Trim removes n characters from one side of the instruction.
+ Trim(n int, d trimDir) Instruction
+ // Copy creates a new instruction with the same content and position.
+ Copy() Instruction
+}
+
+func overlap(over, under Instruction) overlapType {
+ ox, oy := over.Pos()
+ ux, uy := under.Pos()
+ os, us := over.Len(), under.Len()
+
+ // wrong row:
+ if oy != uy {
+ return None
+ }
+
+ // totally covers it
+ if ox <= ux && ox+os >= ux+us {
+ return Covers
+ }
+
+ // Overlap on the left:
+ if ox <= ux && ox+os > ux {
+ return Left
+ }
+
+ // Overlap on the right:
+ if ox < ux+us && ox+os >= ux+us {
+ return Right
+ }
+
+ // centered inside:
+ if ox > ux && ox+os < ux+us {
+ return Within
+ }
+
+ // Only other option is not touching
+ return None
+}
+
+// Squash creates new instruction from overlapping instructions.
+// Whenever an instruction would be written over another instruction,
+// the one under is trimmed to only include the part that is not covered.
+// Three cases are possible:
+// - The under instruction is completely covered by the over instruction.
+// - The under instruction is partially covered on one side
+// - The under instruction is covered in the center (splitting it in two)
+// No matter what, the over instruction never changes so only variations of
+// under needs to be returned.
+func squash(over Instruction, under Instruction, ot overlapType) []Instruction {
+ ox, _ := over.Pos()
+ ux, _ := under.Pos()
+ os, us := over.Len(), under.Len()
+ switch ot {
+ case Covers:
+ return []Instruction{}
+ case Left:
+ overlap := ox + os - ux
+ under.Trim(overlap, ToLeft)
+ return []Instruction{under}
+ case Right:
+ overlap := ux + us - ox
+ under.Trim(overlap, ToRight)
+ return []Instruction{under}
+ case Within:
+ overlapL := ox - ux
+ overlapR := ux + us - (ox + os)
+ left := under.Copy().Trim(overlapL, ToLeft)
+ under.Trim(overlapR, ToRight)
+ return []Instruction{left, under}
+ default:
+ return []Instruction{under}
+ }
+}
+
+func reach(i Instruction) string {
+ x, y := i.Pos()
+ return fmt.Sprintf("\033[%d;%dH", y, x)
+}
+
+// DrawInstruction is a struct that contains content the renderer
+// should draw.
+type DrawInstruction struct {
+ // Content is the string that should be drawn.
+ Content string
+ // Pos is the position in the terminal where the content should be drawn.
+ X int
+ Y int
+ // Decorators is a list of ANSI escape codes used to decorate the content.
+ Decorators []string
+}
+
+func (d DrawInstruction) Pos() (int, int) {
+ return d.X, d.Y
+}
+
+func (d DrawInstruction) Len() int {
+ return len(d.Content)
+}
+
+func (d DrawInstruction) Write(w io.Writer) {
+ msg := reach(d) + strings.Join(d.Decorators, "") + d.Content
+ io.WriteString(w, msg)
+}
+
+func (d DrawInstruction) Trim(n int, from trimDir) Instruction {
+ // Prevents having to error check for negative values.
+ if n < 0 {
+ n = -n
+ }
+ // If n is greater than the length of the content, return nil.
+ if n > len(d.Content) {
+ return nil
+ }
+ switch from {
+ case ToLeft:
+ d.Content = d.Content[n:]
+ d.X += n
+ case ToRight:
+ d.Content = d.Content[:len(d.Content)-n]
+ }
+ return &d
+}
+
+func (d DrawInstruction) Copy() Instruction {
+ return &DrawInstruction{
+ Content: d.Content,
+ X: d.X,
+ Y: d.Y,
+ Decorators: d.Decorators,
+ }
+}
+
+// ClearInstruction is a struct that contains content the renderer should
+// clear. they usually accompany a DrawInstruction (i.e. clear the space
+// behind a moving object) but not always.
+type ClearInstruction struct {
+ // Len is the number of consecutive characters to clear.
+ Size int
+ // Pos is the position in the terminal where the content should be cleared.
+ X int
+ Y int
+}
+
+func (c ClearInstruction) Write(w io.Writer) {
+ // Erase as many characters as the size of the instruction.
+ msg := reach(c) + "\033[K" + strings.Repeat(" ", c.Size)
+ // TODO: maybe implement a way to log stuff like writestring errors
+ io.WriteString(w, msg)
+}
+
+func (c ClearInstruction) Pos() (int, int) {
+ return c.X, c.Y
+}
+
+func (c ClearInstruction) Len() int {
+ if c.Size < 0 {
+ return -c.Size
+ }
+ return c.Size
+}
+
+func (c ClearInstruction) Trim(n int, from trimDir) Instruction {
+ // Prevents having to error check for negative values.
+ if n < 0 {
+ n = -n
+ }
+ // If n is greater than the length of the content, return nil.
+ if n > c.Size {
+ return nil
+ }
+ switch from {
+ case ToLeft:
+ c.Size -= n
+ c.X += n
+ case ToRight:
+ c.Size -= n
+ }
+ return &c
+}
+
+func (c ClearInstruction) Copy() Instruction {
+ return &ClearInstruction{
+ Size: c.Size,
+ X: c.X,
+ Y: c.Y,
+ }
+}