Fix layering

This commit is contained in:
Miroslav Vasilev 2024-04-19 23:11:21 +03:00
parent 6d576939db
commit c2cd1dcf97
6 changed files with 377 additions and 317 deletions

View file

@ -45,7 +45,7 @@ func main() {
layers.Insert(0, rect) layers.Insert(0, rect)
layers.Insert(1, text) layers.Insert(1, text)
layers.Remove(text.UniqueId()) //layers.Remove(text.UniqueId())
events := make(chan tcell.Event) events := make(chan tcell.Event)
quit := make(chan struct{}) quit := make(chan struct{})

78
render/grid.go Normal file
View file

@ -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) {
}

View file

@ -5,50 +5,95 @@ import (
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/google/uuid" "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 { type layeredDrawContainer struct {
id uuid.UUID id uuid.UUID
layers *btree.Map[uint8, []Drawable] layers []*layer
} }
func CreateLayeredDrawContainer() *layeredDrawContainer { func CreateLayeredDrawContainer() *layeredDrawContainer {
container := new(layeredDrawContainer) container := new(layeredDrawContainer)
container.layers = btree.NewMap[uint8, []Drawable](2) container.layers = make([]*layer, 0, 32)
return container return container
} }
func (ldc *layeredDrawContainer) Insert(zLevel uint8, drawable Drawable) { 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 { return
arr = make([]Drawable, 1, 2)
} }
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) { return
ldc.layers.ScanMut(func(key uint8, value []Drawable) bool {
newSlices := slices.DeleteFunc(value, func(v Drawable) bool { return v.UniqueId() == id })
ldc.layers.Set(key, newSlices)
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
} }
// no such layer exists, create it
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() { func (ldc *layeredDrawContainer) Clear() {
ldc.layers = btree.NewMap[uint8, []Drawable](2) ldc.layers = make([]*layer, 0, 32)
} }
func (ldc *layeredDrawContainer) UniqueId() uuid.UUID { func (ldc *layeredDrawContainer) UniqueId() uuid.UUID {
@ -56,11 +101,7 @@ func (ldc *layeredDrawContainer) UniqueId() uuid.UUID {
} }
func (ldc *layeredDrawContainer) Draw(s tcell.Screen) { func (ldc *layeredDrawContainer) Draw(s tcell.Screen) {
ldc.layers.Ascend(0, func(key uint8, value []Drawable) bool { for _, d := range ldc.layers {
for _, d := range value { d.draw(s)
d.Draw(s)
} }
return true
})
} }

140
render/rectangle.go Normal file
View file

@ -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)
}
}
}

View file

@ -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) {
}

91
render/text.go Normal file
View file

@ -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
}
}