summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/render/instructions.go62
-rw-r--r--internal/render/instructions_test.go254
-rw-r--r--internal/render/renderer.go10
3 files changed, 290 insertions, 36 deletions
diff --git a/internal/render/instructions.go b/internal/render/instructions.go
index 685f3f8..b4823f2 100644
--- a/internal/render/instructions.go
+++ b/internal/render/instructions.go
@@ -12,13 +12,13 @@ type (
)
const (
- ToLeft trimDir = iota
- ToRight
- None overlapType = iota
- Covers
- Within
- Left
- Right
+ TrimLeft trimDir = iota
+ TrimRight
+ CoverNone overlapType = iota
+ CoverTotal
+ CoverWithin
+ CoverLeft
+ CoverRight
)
type Instruction interface {
@@ -28,7 +28,7 @@ type Instruction interface {
Pos() (int, int)
// Len returns the length of the instructions content.
Len() int
- // Trim removes n characters from one side of the instruction.
+ // 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
@@ -41,31 +41,31 @@ func overlap(over, under Instruction) overlapType {
// wrong row:
if oy != uy {
- return None
+ return CoverNone
}
// totally covers it
if ox <= ux && ox+os >= ux+us {
- return Covers
+ return CoverTotal
}
// Overlap on the left:
if ox <= ux && ox+os > ux {
- return Left
+ return CoverLeft
}
// Overlap on the right:
if ox < ux+us && ox+os >= ux+us {
- return Right
+ return CoverRight
}
// centered inside:
if ox > ux && ox+os < ux+us {
- return Within
+ return CoverWithin
}
// Only other option is not touching
- return None
+ return CoverNone
}
// Squash creates new instruction from overlapping instructions.
@@ -82,22 +82,22 @@ func squash(over Instruction, under Instruction, ot overlapType) []Instruction {
ux, _ := under.Pos()
os, us := over.Len(), under.Len()
switch ot {
- case Covers:
+ case CoverTotal:
return []Instruction{}
- case Left:
+ case CoverLeft:
overlap := ox + os - ux
- under.Trim(overlap, ToLeft)
+ under = under.Trim(overlap, TrimLeft)
return []Instruction{under}
- case Right:
+ case CoverRight:
overlap := ux + us - ox
- under.Trim(overlap, ToRight)
+ under = under.Trim(overlap, TrimRight)
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}
+ case CoverWithin:
+ overlapR := ox - ux + os
+ overlapL := ux + us - ox
+ right := under.Copy().Trim(overlapR, TrimLeft)
+ left := under.Trim(overlapL, TrimRight)
+ return []Instruction{left, right}
default:
return []Instruction{under}
}
@@ -143,13 +143,13 @@ func (d DrawInstruction) Trim(n int, from trimDir) Instruction {
return nil
}
switch from {
- case ToLeft:
+ case TrimLeft:
d.Content = d.Content[n:]
d.X += n
- case ToRight:
+ case TrimRight:
d.Content = d.Content[:len(d.Content)-n]
}
- return &d
+ return d
}
func (d DrawInstruction) Copy() Instruction {
@@ -200,13 +200,13 @@ func (c ClearInstruction) Trim(n int, from trimDir) Instruction {
return nil
}
switch from {
- case ToLeft:
+ case TrimLeft:
c.Size -= n
c.X += n
- case ToRight:
+ case TrimRight:
c.Size -= n
}
- return &c
+ return c
}
func (c ClearInstruction) Copy() Instruction {
diff --git a/internal/render/instructions_test.go b/internal/render/instructions_test.go
new file mode 100644
index 0000000..837a143
--- /dev/null
+++ b/internal/render/instructions_test.go
@@ -0,0 +1,254 @@
+package render
+
+import (
+ "testing"
+)
+
+const (
+ errFmt = "Failed: '%v'\nExpected %v, got %v"
+ passFmt = "Passed: '%v'\n"
+)
+
+type testTrim struct {
+ title string
+ input Instruction
+ expectedReach string
+ n int // number of characters to trim
+ d trimDir // FROM which side to trim
+ expected Instruction
+}
+
+var TrimCases []testTrim = []testTrim{
+ {
+ "Trim 2 characters from the left",
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ "\033[5;5H",
+ 2,
+ TrimLeft,
+ DrawInstruction{X: 7, Y: 5, Content: "llo"},
+ },
+ {
+ "Trim 2 characters from the right",
+ DrawInstruction{X: 43, Y: 6, Content: "hello"},
+ "\033[6;43H",
+ 2,
+ TrimRight,
+ DrawInstruction{X: 43, Y: 6, Content: "hel"},
+ },
+ {
+ "Trim 2 characters from the left of a Clear",
+ ClearInstruction{X: 8, Y: 4, Size: 5},
+ "\033[4;8H",
+ 2,
+ TrimLeft,
+ ClearInstruction{X: 10, Y: 4, Size: 3},
+ },
+ {
+ "Trim 2 characters from the right of a Clear",
+ ClearInstruction{X: 98, Y: 76, Size: 5},
+ "\033[76;98H",
+ 2,
+ TrimRight,
+ ClearInstruction{X: 98, Y: 76, Size: 3},
+ },
+ {
+ "Trim 10 characters from the left of a Clear of size 5",
+ ClearInstruction{X: 42, Y: 69, Size: 5},
+ "\033[69;42H",
+ 10,
+ TrimLeft,
+ nil,
+ },
+ {
+ "Trim 10 characters from the right of a Draw of size 5",
+ DrawInstruction{X: 420, Y: 1337, Content: "hello"},
+ "\033[1337;420H",
+ 10,
+ TrimRight,
+ nil,
+ },
+}
+
+type testOverlap struct {
+ title string
+ over, under Instruction
+ overlap overlapType
+ expected []Instruction
+}
+
+var OverlapCases []testOverlap = []testOverlap{
+ {
+ "Two draws that perfectly overlap",
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ DrawInstruction{X: 5, Y: 5, Content: "wadup"},
+ CoverTotal,
+ []Instruction{},
+ },
+ {
+ "Draw + Clear that perfectly overlap",
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ ClearInstruction{X: 5, Y: 5, Size: 5},
+ CoverTotal,
+ []Instruction{},
+ },
+ {
+ "Two Clears that perfectly overlap",
+ ClearInstruction{X: 5, Y: 5, Size: 5},
+ ClearInstruction{X: 5, Y: 5, Size: 5},
+ CoverTotal,
+ []Instruction{},
+ },
+ {
+ "Draw 'More than covers' Clear",
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ ClearInstruction{X: 7, Y: 5, Size: 3},
+ CoverTotal,
+ []Instruction{},
+ },
+ {
+ "Clear Squashes Draw on the left",
+ ClearInstruction{X: 2, Y: 5, Size: 5},
+ DrawInstruction{X: 5, Y: 5, Content: "qwertyuiop"},
+ CoverLeft,
+ []Instruction{
+ DrawInstruction{X: 7, Y: 5, Content: "ertyuiop"},
+ },
+ },
+ {
+ "Clear Squashes Draw on the right",
+ ClearInstruction{X: 5, Y: 5, Size: 10},
+ DrawInstruction{X: 2, Y: 5, Content: "qwertyuiop"},
+ CoverRight,
+ []Instruction{
+ DrawInstruction{X: 2, Y: 5, Content: "qwe"},
+ },
+ },
+ {
+ "Draw is within another Draw",
+ DrawInstruction{X: 7, Y: 5, Content: "yo"},
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ CoverWithin,
+ []Instruction{
+ DrawInstruction{X: 5, Y: 5, Content: "he"},
+ DrawInstruction{X: 9, Y: 5, Content: "o"},
+ },
+ },
+ {
+ "Clear is within another Draw",
+ ClearInstruction{X: 10, Y: 5, Size: 5},
+ DrawInstruction{X: 5, Y: 5, Content: "TheQuickBrownFoxJumpsOverTheLazyDog"},
+ CoverWithin,
+ []Instruction{
+ DrawInstruction{X: 5, Y: 5, Content: "TheQu"},
+ DrawInstruction{X: 15, Y: 5, Content: "ownFoxJumpsOverTheLazyDog"},
+ },
+ },
+
+ {
+ "Two Draws that don't overlap but are on the same row",
+ DrawInstruction{X: 5, Y: 5, Content: "hello"},
+ DrawInstruction{X: 55, Y: 5, Content: "greetings"},
+ CoverNone,
+ []Instruction{
+ DrawInstruction{X: 55, Y: 5, Content: "greetings"},
+ },
+ },
+ {
+ "Two Clears on different rows",
+ ClearInstruction{X: 5, Y: 5, Size: 5},
+ ClearInstruction{X: 5, Y: 12, Size: 5},
+ CoverNone,
+ []Instruction{
+ ClearInstruction{X: 5, Y: 12, Size: 5},
+ },
+ },
+}
+
+func InstructionEquals(a, b Instruction) bool {
+ var drwA, drwB DrawInstruction
+ drwAOk, clrAOk := false, false
+ var clrA, clrB ClearInstruction
+ switch a := a.(type) {
+ case DrawInstruction:
+ drwA = a
+ drwAOk = true
+ case ClearInstruction:
+ clrA = a
+ clrAOk = true
+
+ }
+ switch b := b.(type) {
+ case DrawInstruction:
+ drwB = b
+ if clrAOk { // A is of opposite type (clear)
+ return false
+ }
+ case ClearInstruction:
+ clrB = b
+ if drwAOk { // A is of opposite type (draw)
+ return false
+ }
+ }
+ // If both are draws
+ if drwAOk {
+ if drwA.Content != drwB.Content {
+ return false
+ }
+ if drwA.X != drwB.X || drwA.Y != drwB.Y {
+ return false
+ }
+ }
+ // If both are clears
+ if clrAOk {
+ if clrA.Size != clrB.Size {
+ return false
+ }
+ if clrA.X != clrB.X || clrA.Y != clrB.Y {
+ return false
+ }
+ }
+ return true
+}
+
+func TestTrim(t *testing.T) {
+ for _, tc := range TrimCases {
+ result := tc.input.Trim(tc.n, tc.d)
+ if !InstructionEquals(result, tc.expected) {
+ t.Errorf(errFmt, tc.title, tc.expected, result)
+ }
+ t.Logf(passFmt, tc.title)
+ }
+}
+
+func TestReach(t *testing.T) {
+ for _, tc := range TrimCases {
+ if r := reach(tc.input); r != tc.expectedReach {
+ t.Errorf(errFmt, tc.title, tc.expectedReach, r)
+ }
+ t.Logf(passFmt, tc.title)
+ }
+}
+
+func TestOverlap(t *testing.T) {
+ for _, tc := range OverlapCases {
+ if ot := overlap(tc.over, tc.under); ot != tc.overlap {
+ t.Errorf(errFmt, tc.title, tc.overlap, ot)
+ }
+ t.Logf(passFmt, tc.title)
+ }
+}
+
+func TestSquash(t *testing.T) {
+ for _, tc := range OverlapCases {
+ res := squash(tc.over, tc.under, tc.overlap)
+ if len(res) != len(tc.expected) {
+ t.Errorf(errFmt, tc.title, len(tc.expected), len(res))
+ }
+ for i, r := range res {
+ if !InstructionEquals(r, tc.expected[i]) {
+ t.Errorf(errFmt, tc.title, tc.expected[i], r)
+ }
+ }
+ t.Logf(passFmt, tc.title)
+ }
+}
diff --git a/internal/render/renderer.go b/internal/render/renderer.go
index c527413..b851e1e 100644
--- a/internal/render/renderer.go
+++ b/internal/render/renderer.go
@@ -125,7 +125,7 @@ func (r *Renderer) DrawFrame() {
// squash overlapping clear instructions
for _, drw := range drwBuf {
for j, clr := range clrBuf {
- if ot := overlap(drw, clr); ot != None {
+ if ot := overlap(drw, clr); ot != CoverNone {
newClr := squash(drw, clr, ot)
switch len(newClr) {
case 0: // Complete overlap -> delete the one under
@@ -143,7 +143,7 @@ func (r *Renderer) DrawFrame() {
for i := 0; i < len(drwBuf); i++ {
for j := i + 1; j < len(drwBuf); j++ {
older, newer := drwBuf[i], drwBuf[j]
- if ot := overlap(older, newer); ot != None {
+ if ot := overlap(older, newer); ot != CoverNone {
newDrw := squash(newer, older, ot)
switch len(newDrw) {
case 0: // Complete overlap -> delete the one under
@@ -158,10 +158,10 @@ func (r *Renderer) DrawFrame() {
}
}
// Draw
- for _, clr := range clrBuf {
- clr.Write(r.stream)
- }
for _, drw := range drwBuf {
drw.Write(r.stream)
}
+ for _, clr := range clrBuf {
+ clr.Write(r.stream)
+ }
}