diff --git a/main.go b/main.go index 0e66e91..416f6f3 100644 --- a/main.go +++ b/main.go @@ -45,7 +45,7 @@ func main() { layers.Insert(0, rect) layers.Insert(1, text) - layers.Remove(text.UniqueId()) + //layers.Remove(text.UniqueId()) events := make(chan tcell.Event) quit := make(chan struct{}) diff --git a/render/grid.go b/render/grid.go new file mode 100644 index 0000000..318a4fa --- /dev/null +++ b/render/grid.go @@ -0,0 +1,78 @@ +package render + +import ( + "mvvasilev/last_light/util" + + "github.com/gdamore/tcell/v2" + "github.com/google/uuid" +) + +type grid struct { + id uuid.UUID + + internalCellSize util.Size + numCellsHorizontal uint16 + numCellsVertical uint16 + position util.Position + style tcell.Style + + northBorder rune + westBorder rune + eastBorder rune + southBorder rune + + nwCorner rune + swCorner rune + seCorner rune + neCorner rune + + verticalTJunction rune + horizontalTJunction rune + crossJunction rune + + fillRune rune +} + +func CreateGrid( + x uint16, + y uint16, + cellWidth uint16, + cellHeight uint16, + numCellsHorizontal uint16, + numCellsVertical uint16, + nwCorner, northBorder, neCorner, + westBorder, fillRune, eastBorder, + swCorner, southBorder, seCorner, + verticalTJunction, horizontalTJunction, + crossJunction rune, + style tcell.Style, +) grid { + return grid{ + id: uuid.New(), + internalCellSize: util.SizeOf(cellWidth, cellHeight), + numCellsHorizontal: numCellsHorizontal, + numCellsVertical: numCellsVertical, + position: util.PositionAt(x, y), + style: style, + northBorder: northBorder, + eastBorder: eastBorder, + southBorder: southBorder, + westBorder: westBorder, + nwCorner: nwCorner, + seCorner: seCorner, + swCorner: swCorner, + neCorner: neCorner, + fillRune: fillRune, + verticalTJunction: verticalTJunction, + horizontalTJunction: horizontalTJunction, + crossJunction: crossJunction, + } +} + +func (g grid) UniqueId() uuid.UUID { + return g.id +} + +func (g grid) Draw(s tcell.Screen) { + +} diff --git a/render/layers.go b/render/layers.go index 1301429..eda9053 100644 --- a/render/layers.go +++ b/render/layers.go @@ -5,50 +5,95 @@ import ( "github.com/gdamore/tcell/v2" "github.com/google/uuid" - "github.com/tidwall/btree" ) +type layer struct { + zIndex uint8 + contents []Drawable +} + +func makeLayer(zIndex uint8) *layer { + l := new(layer) + + l.zIndex = zIndex + l.contents = make([]Drawable, 0, 1) + + return l +} + +func (l *layer) push(drawable Drawable) { + l.contents = append(l.contents, drawable) +} + +func (l *layer) remove(uuid uuid.UUID) { + l.contents = slices.DeleteFunc(l.contents, func(d Drawable) bool { + return d.UniqueId() == uuid + }) +} + +func (l *layer) draw(s tcell.Screen) { + for _, d := range l.contents { + d.Draw(s) + } +} + type layeredDrawContainer struct { id uuid.UUID - layers *btree.Map[uint8, []Drawable] + layers []*layer } func CreateLayeredDrawContainer() *layeredDrawContainer { container := new(layeredDrawContainer) - container.layers = btree.NewMap[uint8, []Drawable](2) + container.layers = make([]*layer, 0, 32) return container } func (ldc *layeredDrawContainer) Insert(zLevel uint8, drawable Drawable) { - arr, found := ldc.layers.Get(zLevel) + // if no layers exist, just insert a new one + if len(ldc.layers) == 0 { + l := makeLayer(zLevel) + l.push(drawable) + ldc.layers = append(ldc.layers, l) - if !found { - arr = make([]Drawable, 1, 2) + return } - arr = append(arr, drawable) + // find a layer with this z-index + i := slices.IndexFunc(ldc.layers, func(l *layer) bool { + return l.zIndex == zLevel + }) - ldc.layers.Set(zLevel, arr) -} + // z index already exists + if i > 0 { + l := ldc.layers[i] + l.push(drawable) -func (ldc *layeredDrawContainer) Remove(id uuid.UUID) { - ldc.layers.ScanMut(func(key uint8, value []Drawable) bool { - newSlices := slices.DeleteFunc(value, func(v Drawable) bool { return v.UniqueId() == id }) + return + } - ldc.layers.Set(key, newSlices) + // no such layer exists, create it - if len(newSlices) != len(value) { - return false // the slice has been modified, we have found the drawable. Return false to stop iteration. - } else { - return true // we haven't found it yet, keep going - } + l := makeLayer(zLevel) + l.push(drawable) + + ldc.layers = append(ldc.layers, l) + + // order layers ascending + slices.SortFunc(ldc.layers, func(l1 *layer, l2 *layer) int { + return int(l1.zIndex) - int(l2.zIndex) }) } +func (ldc *layeredDrawContainer) Remove(id uuid.UUID) { + for _, l := range ldc.layers { + l.remove(id) + } +} + func (ldc *layeredDrawContainer) Clear() { - ldc.layers = btree.NewMap[uint8, []Drawable](2) + ldc.layers = make([]*layer, 0, 32) } func (ldc *layeredDrawContainer) UniqueId() uuid.UUID { @@ -56,11 +101,7 @@ func (ldc *layeredDrawContainer) UniqueId() uuid.UUID { } func (ldc *layeredDrawContainer) Draw(s tcell.Screen) { - ldc.layers.Ascend(0, func(key uint8, value []Drawable) bool { - for _, d := range value { - d.Draw(s) - } - - return true - }) + for _, d := range ldc.layers { + d.draw(s) + } } diff --git a/render/rectangle.go b/render/rectangle.go new file mode 100644 index 0000000..ed7f3fa --- /dev/null +++ b/render/rectangle.go @@ -0,0 +1,140 @@ +package render + +import ( + "mvvasilev/last_light/util" + + "github.com/gdamore/tcell/v2" + "github.com/google/uuid" +) + +type rectangle struct { + id uuid.UUID + + size util.Size + position util.Position + style tcell.Style + + northBorder rune + westBorder rune + eastBorder rune + southBorder rune + + nwCorner rune + swCorner rune + seCorner rune + neCorner rune + + fillRune rune +} + +func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, borderRune rune, fillRune rune, style tcell.Style) rectangle { + return CreateRectangle( + x, y, width, height, + borderRune, borderRune, borderRune, + borderRune, fillRune, borderRune, + borderRune, borderRune, borderRune, + style, + ) +} + +// CreateRectangle( +// +// x, y, width, height, +// '┌', '─', '┐', +// '│', ' ', '│', +// '└', '─', '┘', +// style +// +// ) +func CreateRectangle( + x uint16, + y uint16, + width uint16, + height uint16, + nwCorner, northBorder, neCorner, + westBorder, fillRune, eastBorder, + swCorner, southBorder, seCorner rune, + style tcell.Style, +) rectangle { + return rectangle{ + id: uuid.New(), + size: util.SizeOf(width, height), + position: util.PositionAt(x, y), + style: style, + northBorder: northBorder, + eastBorder: eastBorder, + southBorder: southBorder, + westBorder: westBorder, + nwCorner: nwCorner, + seCorner: seCorner, + swCorner: swCorner, + neCorner: neCorner, + fillRune: fillRune, + } +} + +func (rect rectangle) UniqueId() uuid.UUID { + return rect.id +} + +func (rect rectangle) Draw(s tcell.Screen) { + width := rect.size.Width() + height := rect.size.Height() + x := rect.position.X() + y := rect.position.Y() + + for h := range height { + for w := range width { + + // nw corner + if w == 0 && h == 0 { + s.SetContent(x+w, y+h, rect.nwCorner, nil, rect.style) + continue + } + + // ne corner + if w == (width-1) && h == 0 { + s.SetContent(x+w, y+h, rect.neCorner, nil, rect.style) + continue + } + + // sw corner + if w == 0 && h == (height-1) { + s.SetContent(x+w, y+h, rect.swCorner, nil, rect.style) + continue + } + + // se corner + if w == (width-1) && h == (height-1) { + s.SetContent(x+w, y+h, rect.seCorner, nil, rect.style) + continue + } + + // north border + if h == 0 && (w != 0 && w != (width-1)) { + s.SetContent(x+w, y+h, rect.northBorder, nil, rect.style) + continue + } + + // south border + if h == (height-1) && (w != 0 && w != (width-1)) { + s.SetContent(x+w, y+h, rect.southBorder, nil, rect.style) + continue + } + + // west border + if w == 0 && (h != 0 && h != (height-1)) { + s.SetContent(x+w, y+h, rect.westBorder, nil, rect.style) + continue + } + + // east border + if w == (width-1) && (h != 0 && h != (height-1)) { + s.SetContent(x+w, y+h, rect.eastBorder, nil, rect.style) + continue + } + + s.SetContent(x+w, y+h, rect.fillRune, nil, rect.style) + } + } +} diff --git a/render/render.go b/render/render.go deleted file mode 100644 index aaef03c..0000000 --- a/render/render.go +++ /dev/null @@ -1,290 +0,0 @@ -package render - -import ( - "mvvasilev/last_light/util" - "strings" - "unicode/utf8" - - "github.com/gdamore/tcell/v2" - "github.com/google/uuid" -) - -type Drawable interface { - UniqueId() uuid.UUID - Draw(s tcell.Screen) -} - -type rectangle struct { - id uuid.UUID - - size util.Size - position util.Position - style tcell.Style - - northBorder rune - westBorder rune - eastBorder rune - southBorder rune - - nwCorner rune - swCorner rune - seCorner rune - neCorner rune - - fillRune rune -} - -func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, borderRune rune, fillRune rune, style tcell.Style) rectangle { - return CreateRectangle( - x, y, width, height, - borderRune, borderRune, borderRune, - borderRune, fillRune, borderRune, - borderRune, borderRune, borderRune, - style, - ) -} - -// CreateRectangle( -// -// x, y, width, height, -// '┌', '─', '┐', -// '│', ' ', '│', -// '└', '─', '┘', -// style -// -// ) -func CreateRectangle( - x uint16, - y uint16, - width uint16, - height uint16, - nwCorner, northBorder, neCorner, - westBorder, fillRune, eastBorder, - swCorner, southBorder, seCorner rune, - style tcell.Style, -) rectangle { - return rectangle{ - id: uuid.New(), - size: util.SizeOf(width, height), - position: util.PositionAt(x, y), - style: style, - northBorder: northBorder, - eastBorder: eastBorder, - southBorder: southBorder, - westBorder: westBorder, - nwCorner: nwCorner, - seCorner: seCorner, - swCorner: swCorner, - neCorner: neCorner, - fillRune: fillRune, - } -} - -func (rect rectangle) UniqueId() uuid.UUID { - return rect.id -} - -func (rect rectangle) Draw(s tcell.Screen) { - width := rect.size.Width() - height := rect.size.Height() - x := rect.position.X() - y := rect.position.Y() - - for h := range height { - for w := range width { - - // nw corner - if w == 0 && h == 0 { - s.SetContent(x+w, y+h, rect.nwCorner, nil, rect.style) - continue - } - - // ne corner - if w == (width-1) && h == 0 { - s.SetContent(x+w, y+h, rect.neCorner, nil, rect.style) - continue - } - - // sw corner - if w == 0 && h == (height-1) { - s.SetContent(x+w, y+h, rect.swCorner, nil, rect.style) - continue - } - - // se corner - if w == (width-1) && h == (height-1) { - s.SetContent(x+w, y+h, rect.seCorner, nil, rect.style) - continue - } - - // north border - if h == 0 && (w != 0 && w != (width-1)) { - s.SetContent(x+w, y+h, rect.northBorder, nil, rect.style) - continue - } - - // south border - if h == (height-1) && (w != 0 && w != (width-1)) { - s.SetContent(x+w, y+h, rect.southBorder, nil, rect.style) - continue - } - - // west border - if w == 0 && (h != 0 && h != (height-1)) { - s.SetContent(x+w, y+h, rect.westBorder, nil, rect.style) - continue - } - - // east border - if w == (width-1) && (h != 0 && h != (height-1)) { - s.SetContent(x+w, y+h, rect.eastBorder, nil, rect.style) - continue - } - - s.SetContent(x+w, y+h, rect.fillRune, nil, rect.style) - } - } - -} - -type text struct { - id uuid.UUID - content []string - position util.Position - size util.Size - style tcell.Style -} - -func CreateText( - x, y uint16, - width, height uint16, - content string, - style tcell.Style, -) text { - return text{ - id: uuid.New(), - content: strings.Split(content, " "), - style: style, - size: util.SizeOf(width, height), - position: util.PositionAt(x, y), - } -} - -func (t text) UniqueId() uuid.UUID { - return t.id -} - -func (t text) Draw(s tcell.Screen) { - width := t.size.Width() - height := t.size.Height() - x := t.position.X() - y := t.position.Y() - - currentHPos := 0 - currentVPos := 0 - - drawText := func(text string) { - for i, r := range text { - s.SetContent(x+currentHPos+i, y+currentVPos, r, nil, t.style) - } - } - - for _, s := range t.content { - runeCount := utf8.RuneCountInString(s) - - if currentVPos > height { - break - } - - // The current word cannot fit within the remaining space on the line - if runeCount > (width - currentHPos) { - currentVPos += 1 // next line - currentHPos = 0 // reset to start of line - - drawText(s + " ") - currentHPos += runeCount + 1 - - continue - } - - // The current word fits exactly within the remaining space on the line - if runeCount == (width - currentHPos) { - drawText(s) - - currentVPos += 1 // next line - currentHPos = 0 // reset to start of line - - continue - } - - // The current word fits within the remaining space, and there's more space left over - drawText(s + " ") - currentHPos += runeCount + 1 // add +1 to account for space after word - } -} - -type grid struct { - id uuid.UUID - - internalCellSize util.Size - numCellsHorizontal uint16 - numCellsVertical uint16 - position util.Position - style tcell.Style - - northBorder rune - westBorder rune - eastBorder rune - southBorder rune - - nwCorner rune - swCorner rune - seCorner rune - neCorner rune - - verticalTJunction rune - horizontalTJunction rune - crossJunction rune - - fillRune rune -} - -func CreateGrid( - x uint16, - y uint16, - cellWidth uint16, - cellHeight uint16, - numCellsHorizontal uint16, - numCellsVertical uint16, - nwCorner, northBorder, neCorner, - westBorder, fillRune, eastBorder, - swCorner, southBorder, seCorner, - verticalTJunction, horizontalTJunction, - crossJunction rune, - style tcell.Style, -) grid { - return grid{ - id: uuid.New(), - internalCellSize: util.SizeOf(cellWidth, cellHeight), - numCellsHorizontal: numCellsHorizontal, - numCellsVertical: numCellsVertical, - position: util.PositionAt(x, y), - style: style, - northBorder: northBorder, - eastBorder: eastBorder, - southBorder: southBorder, - westBorder: westBorder, - nwCorner: nwCorner, - seCorner: seCorner, - swCorner: swCorner, - neCorner: neCorner, - fillRune: fillRune, - verticalTJunction: verticalTJunction, - horizontalTJunction: horizontalTJunction, - crossJunction: crossJunction, - } -} - -func (g grid) Draw(s tcell.Screen) { - -} diff --git a/render/text.go b/render/text.go new file mode 100644 index 0000000..a8181a1 --- /dev/null +++ b/render/text.go @@ -0,0 +1,91 @@ +package render + +import ( + "mvvasilev/last_light/util" + "strings" + "unicode/utf8" + + "github.com/gdamore/tcell/v2" + "github.com/google/uuid" +) + +type Drawable interface { + UniqueId() uuid.UUID + Draw(s tcell.Screen) +} + +type text struct { + id uuid.UUID + content []string + position util.Position + size util.Size + style tcell.Style +} + +func CreateText( + x, y uint16, + width, height uint16, + content string, + style tcell.Style, +) text { + return text{ + id: uuid.New(), + content: strings.Split(content, " "), + style: style, + size: util.SizeOf(width, height), + position: util.PositionAt(x, y), + } +} + +func (t text) UniqueId() uuid.UUID { + return t.id +} + +func (t text) Draw(s tcell.Screen) { + width := t.size.Width() + height := t.size.Height() + x := t.position.X() + y := t.position.Y() + + currentHPos := 0 + currentVPos := 0 + + drawText := func(text string) { + for i, r := range text { + s.SetContent(x+currentHPos+i, y+currentVPos, r, nil, t.style) + } + } + + for _, s := range t.content { + runeCount := utf8.RuneCountInString(s) + + if currentVPos > height { + break + } + + // The current word cannot fit within the remaining space on the line + if runeCount > (width - currentHPos) { + currentVPos += 1 // next line + currentHPos = 0 // reset to start of line + + drawText(s + " ") + currentHPos += runeCount + 1 + + continue + } + + // The current word fits exactly within the remaining space on the line + if runeCount == (width - currentHPos) { + drawText(s) + + currentVPos += 1 // next line + currentHPos = 0 // reset to start of line + + continue + } + + // The current word fits within the remaining space, and there's more space left over + drawText(s + " ") + currentHPos += runeCount + 1 // add +1 to account for space after word + } +}