Main Menu, main play state, pause menu

This commit is contained in:
Miroslav Vasilev 2024-04-24 17:11:33 +03:00
parent dce7d29a99
commit c0f80f0e0c
24 changed files with 1053 additions and 86 deletions

41
game/game.go Normal file
View file

@ -0,0 +1,41 @@
package game
import (
"mvvasilev/last_light/game/state"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type Game struct {
state state.GameState
}
func CreateGame() *Game {
game := new(Game)
game.state = state.NewMainMenuState()
return game
}
func (g *Game) Input(ev *tcell.EventKey) {
g.state.OnInput(ev)
}
func (g *Game) Tick(dt int64) bool {
s := g.state.OnTick(dt)
switch s.(type) {
case *state.QuitState:
return false
}
g.state = s
return true
}
func (g *Game) Draw(v views.View) {
g.state.OnDraw(v)
}

30
game/model/entity.go Normal file
View file

@ -0,0 +1,30 @@
package model
import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type Direction int
const (
Up Direction = iota
Down
Left
Right
)
type Entity interface {
UniqueId() uuid.UUID
Draw(v views.View)
Input(e *tcell.EventKey)
Tick(dt int64)
}
type MovableEntity interface {
Position() util.Position
Move(dir Direction)
}

76
game/model/player.go Normal file
View file

@ -0,0 +1,76 @@
package model
import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type Player struct {
id uuid.UUID
position util.Position
style tcell.Style
}
func CreatePlayer(x, y uint16, style tcell.Style) *Player {
p := new(Player)
p.id = uuid.New()
p.position = util.PositionAt(x, y)
return p
}
func (p *Player) UniqueId() uuid.UUID {
return p.id
}
func (p *Player) Move(dir Direction) {
x, y := p.position.XYUint16()
switch dir {
case Up:
p.position = util.PositionAt(x, y-1)
case Down:
p.position = util.PositionAt(x, y+1)
case Left:
p.position = util.PositionAt(x-1, y)
case Right:
p.position = util.PositionAt(x+1, y)
}
}
func (p *Player) Draw(v views.View) {
x, y := p.position.XY()
v.SetContent(x, y, '@', nil, p.style)
}
func (p *Player) Input(e *tcell.EventKey) {
switch e.Key() {
case tcell.KeyUp:
p.Move(Up)
case tcell.KeyDown:
p.Move(Down)
case tcell.KeyLeft:
p.Move(Left)
case tcell.KeyRight:
p.Move(Right)
case tcell.KeyRune:
switch e.Rune() {
case 'w':
p.Move(Up)
case 'a':
p.Move(Left)
case 's':
p.Move(Down)
case 'd':
p.Move(Right)
}
}
}
func (p *Player) Tick(dt int64) {
}

20
game/state/gameState.go Normal file
View file

@ -0,0 +1,20 @@
package state
import (
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type GameState interface {
OnInput(e *tcell.EventKey)
OnTick(dt int64) GameState
OnDraw(c views.View)
}
type PausableState interface {
Pause()
Unpause()
SetPaused(paused bool)
GameState
}

View file

@ -0,0 +1,87 @@
package state
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/ui"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type MainMenuState struct {
menuTitle *render.Raw
buttons []*ui.UISimpleButton
currButtonSelected int
quitGame bool
startNewGame bool
}
func NewMainMenuState() *MainMenuState {
state := new(MainMenuState)
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
state.menuTitle = render.CreateRawDrawable(
11, 1, tcell.StyleDefault.Attributes(tcell.AttrBold).Foreground(tcell.ColorYellow),
" | | | _) | | ",
" | _` | __| __| | | _` | __ \\ __|",
" | ( | \\__ \\ | | | ( | | | | | ",
"_____| \\__,_| ____/ \\__| _____| _| \\__, | _| |_| \\__|",
" |___/ ",
)
state.buttons = make([]*ui.UISimpleButton, 0)
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 7, "New Game", tcell.StyleDefault, highlightStyle, func() {
state.startNewGame = true
}))
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 9, "Load Game", tcell.StyleDefault, highlightStyle, func() {
}))
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 11, "Quit", tcell.StyleDefault, highlightStyle, func() {
state.quitGame = true
}))
state.currButtonSelected = 0
state.buttons[state.currButtonSelected].Highlight()
return state
}
func (mms *MainMenuState) OnInput(e *tcell.EventKey) {
if e.Key() == tcell.KeyDown {
mms.buttons[mms.currButtonSelected].Unhighlight()
mms.currButtonSelected = util.LimitIncrement(mms.currButtonSelected, 2)
mms.buttons[mms.currButtonSelected].Highlight()
}
if e.Key() == tcell.KeyUp {
mms.buttons[mms.currButtonSelected].Unhighlight()
mms.currButtonSelected = util.LimitDecrement(mms.currButtonSelected, 0)
mms.buttons[mms.currButtonSelected].Highlight()
}
if e.Key() == tcell.KeyEnter {
mms.buttons[mms.currButtonSelected].Select()
}
}
func (mms *MainMenuState) OnTick(dt int64) GameState {
if mms.quitGame {
return &QuitState{}
}
if mms.startNewGame {
return BeginPlayingState()
}
return mms
}
func (mms *MainMenuState) OnDraw(c views.View) {
mms.menuTitle.Draw(c)
for _, b := range mms.buttons {
b.Draw(c)
}
}

View file

@ -0,0 +1,108 @@
package state
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/ui"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type PauseGameState struct {
prevState PausableState
unpauseGame bool
returnToMainMenu bool
pauseMenuWindow *ui.UIWindow
buttons []*ui.UISimpleButton
currButtonSelected int
}
func PauseGame(prevState PausableState) *PauseGameState {
s := new(PauseGameState)
s.prevState = prevState
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
s.pauseMenuWindow = ui.CreateWindow(uint16(render.TERMINAL_SIZE_WIDTH)/2-15, uint16(render.TERMINAL_SIZE_HEIGHT)/2-7, 30, 14, "PAUSED", tcell.StyleDefault)
s.buttons = make([]*ui.UISimpleButton, 0)
s.buttons = append(
s.buttons,
ui.CreateSimpleButton(
uint16(s.pauseMenuWindow.Position().X())+3,
uint16(s.pauseMenuWindow.Position().Y())+1,
"Resume",
tcell.StyleDefault,
highlightStyle,
func() {
s.unpauseGame = true
},
),
)
s.buttons = append(
s.buttons,
ui.CreateSimpleButton(
uint16(s.pauseMenuWindow.Position().X())+3,
uint16(s.pauseMenuWindow.Position().Y())+3,
"Exit To Main Menu",
tcell.StyleDefault,
highlightStyle,
func() {
s.returnToMainMenu = true
},
),
)
s.currButtonSelected = 0
s.buttons[s.currButtonSelected].Highlight()
return s
}
func (pg *PauseGameState) OnInput(e *tcell.EventKey) {
if e.Key() == tcell.KeyEsc {
pg.unpauseGame = true
}
if e.Key() == tcell.KeyDown {
pg.buttons[pg.currButtonSelected].Unhighlight()
pg.currButtonSelected = util.LimitIncrement(pg.currButtonSelected, 1)
pg.buttons[pg.currButtonSelected].Highlight()
}
if e.Key() == tcell.KeyUp {
pg.buttons[pg.currButtonSelected].Unhighlight()
pg.currButtonSelected = util.LimitDecrement(pg.currButtonSelected, 0)
pg.buttons[pg.currButtonSelected].Highlight()
}
if e.Key() == tcell.KeyEnter {
pg.buttons[pg.currButtonSelected].Select()
}
}
func (pg *PauseGameState) OnTick(dt int64) GameState {
if pg.unpauseGame {
pg.prevState.Unpause()
return pg.prevState
}
if pg.returnToMainMenu {
return NewMainMenuState()
}
return pg
}
func (pg *PauseGameState) OnDraw(c views.View) {
pg.prevState.OnDraw(c)
pg.pauseMenuWindow.Draw(c)
for _, b := range pg.buttons {
b.Draw(c)
}
}

View file

@ -0,0 +1,56 @@
package state
import (
"mvvasilev/last_light/game/model"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type PlayingState struct {
player *model.Player
pauseGame bool
}
func BeginPlayingState() *PlayingState {
s := new(PlayingState)
s.player = model.CreatePlayer(10, 10, tcell.StyleDefault)
return s
}
func (ps *PlayingState) Pause() {
ps.pauseGame = true
}
func (ps *PlayingState) Unpause() {
ps.pauseGame = false
}
func (ps *PlayingState) SetPaused(paused bool) {
ps.pauseGame = paused
}
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
ps.player.Input(e)
if e.Key() == tcell.KeyEsc {
ps.pauseGame = true
}
}
func (ps *PlayingState) OnTick(dt int64) GameState {
ps.player.Tick(dt)
if ps.pauseGame {
return PauseGame(ps)
}
return ps
}
func (ps *PlayingState) OnDraw(c views.View) {
ps.player.Draw(c)
}

21
game/state/quitState.go Normal file
View file

@ -0,0 +1,21 @@
package state
import (
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type QuitState struct {
}
func (q *QuitState) OnInput(e *tcell.EventKey) {
}
func (q *QuitState) OnTick(dt int64) GameState {
return q
}
func (q *QuitState) OnDraw(c views.View) {
}

View file

@ -1 +0,0 @@
package main

42
main.go
View file

@ -3,6 +3,7 @@ package main
import (
"fmt"
"log"
"mvvasilev/last_light/game"
"mvvasilev/last_light/render"
"os"
@ -18,46 +19,33 @@ func main() {
log.Fatalf("%~v", err)
}
g := game.CreateGame()
c.HandleInput(func(ev *tcell.EventKey) {
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
if ev.Key() == tcell.KeyCtrlC {
c.Stop()
os.Exit(0)
}
g.Input(ev)
})
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
rect := render.CreateRectangle(
0, 0, 80, 24,
'┌', '─', '┐',
'│', '#', '│',
'└', '─', '┘',
false, true, defStyle,
)
// text := render.CreateText(1, 2, 8, 8, "Hello World! How are you today?", defStyle)
// grid := render.CreateGrid(
// 11, 1, 3, 3, 3, 3,
// '┌', '─', '┬', '┐',
// '│', '#', '│', '│',
// '├', '─', '┼', '┤',
// '└', '─', '┴', '┘',
// defStyle,
// )
layers := render.CreateLayeredDrawContainer()
layers.Insert(0, rect)
// layers.Insert(1, text)
// layers.Insert(0, grid)
c.HandleRender(func(view views.View, deltaTime int64) {
fps := 1_000_000 / deltaTime
fpsText := render.CreateText(0, 0, 16, 1, fmt.Sprintf("%v FPS", fps), defStyle)
layers.Draw(view)
keepGoing := g.Tick(deltaTime)
if !keepGoing {
c.Stop()
os.Exit(0)
}
g.Draw(view)
fpsText.Draw(view)
})

View file

@ -22,7 +22,7 @@ type Drawable interface {
Draw(v views.View)
}
type renderContext struct {
type RenderContext struct {
screen tcell.Screen
view *views.ViewPort
defaultStyle tcell.Style
@ -36,7 +36,7 @@ type renderContext struct {
inputHandler func(ev *tcell.EventKey)
}
func CreateRenderContext() (*renderContext, error) {
func CreateRenderContext() (*RenderContext, error) {
s, err := tcell.NewScreen()
if err != nil {
@ -77,7 +77,7 @@ func CreateRenderContext() (*renderContext, error) {
go s.ChannelEvents(events, quit)
context := new(renderContext)
context := new(RenderContext)
context.screen = s
context.defaultStyle = defStyle
@ -88,19 +88,19 @@ func CreateRenderContext() (*renderContext, error) {
return context, nil
}
func (c *renderContext) Stop() {
func (c *RenderContext) Stop() {
c.screen.Fini()
}
func (c *renderContext) HandleRender(renderHandler func(view views.View, deltaTime int64)) {
func (c *RenderContext) HandleRender(renderHandler func(view views.View, deltaTime int64)) {
c.renderHandler = renderHandler
}
func (c *renderContext) HandleInput(inputHandler func(ev *tcell.EventKey)) {
func (c *RenderContext) HandleInput(inputHandler func(ev *tcell.EventKey)) {
c.inputHandler = inputHandler
}
func (c *renderContext) onResize(ev *tcell.EventResize) {
func (c *RenderContext) onResize(ev *tcell.EventResize) {
width, height := ev.Size()
c.screen.Clear()
@ -115,7 +115,7 @@ func (c *renderContext) onResize(ev *tcell.EventResize) {
c.screen.Sync()
}
func (c *renderContext) BeginRendering() {
func (c *RenderContext) BeginRendering() {
c.lastRenderTime = time.Now()
for {

View file

@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
)
type grid struct {
type Grid struct {
id uuid.UUID
internalCellSize util.Size
@ -45,7 +45,7 @@ func CreateSimpleGrid(
numCellsHorizontal, numCellsVertical uint16,
borderRune, fillRune rune,
style tcell.Style,
) grid {
) Grid {
return CreateGrid(
x, y, cellWidth, cellHeight, numCellsHorizontal, numCellsVertical,
borderRune, borderRune, borderRune, borderRune,
@ -72,8 +72,8 @@ func CreateGrid(
horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction,
swCorner, southBorder, verticalUpwardsTJunction, seCorner rune,
style tcell.Style,
) grid {
return grid{
) Grid {
return Grid{
id: uuid.New(),
internalCellSize: util.SizeOf(cellWidth, cellHeight),
numCellsHorizontal: numCellsHorizontal,
@ -100,7 +100,7 @@ func CreateGrid(
}
}
func (g grid) UniqueId() uuid.UUID {
func (g Grid) UniqueId() uuid.UUID {
return g.id
}
@ -117,9 +117,11 @@ func (g grid) UniqueId() uuid.UUID {
// # # # #
// # # # #
// C###T###T###C
func (g grid) drawBorders(v views.View) {
width := 2 + (g.internalCellSize.Width() * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal) - 1)
height := 2 + (g.internalCellSize.Height() * int(g.numCellsVertical)) + (int(g.numCellsVertical) - 1)
func (g Grid) drawBorders(v views.View) {
iCellSizeWidth := g.internalCellSize.Width()
iCellSizeHeight := g.internalCellSize.Height()
width := 1 + (iCellSizeWidth * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal))
height := 1 + (iCellSizeHeight * int(g.numCellsVertical)) + (int(g.numCellsVertical))
x := g.position.X()
y := g.position.Y()
@ -128,22 +130,54 @@ func (g grid) drawBorders(v views.View) {
v.SetContent(x, y+height-1, g.swCorner, nil, g.style)
v.SetContent(x+width-1, y+height-1, g.seCorner, nil, g.style)
for w := range width - 2 {
v.SetContent(1+w, y, g.northBorder, nil, g.style)
v.SetContent(1+w, y+height-1, g.southBorder, nil, g.style)
for w := 1; w < width-1; w++ {
for iw := 1; iw < int(g.numCellsVertical); iw++ {
if w%(iCellSizeWidth+1) == 0 {
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.crossJunction, nil, g.style)
continue
}
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.internalHorizontalBorder, nil, g.style)
}
if w%(iCellSizeWidth+1) == 0 {
v.SetContent(x+w, y, g.verticalDownwardsTJunction, nil, g.style)
v.SetContent(x+w, y+height-1, g.verticalUpwardsTJunction, nil, g.style)
continue
}
v.SetContent(x+w, y, g.northBorder, nil, g.style)
v.SetContent(x+w, y+height-1, g.southBorder, nil, g.style)
}
for h := range height - 2 {
v.SetContent(x, 1+h, g.westBorder, nil, g.style)
v.SetContent(x+width-1, 1+h, g.eastBorder, nil, g.style)
for h := 1; h < height-1; h++ {
for ih := 1; ih < int(g.numCellsHorizontal); ih++ {
if h%(iCellSizeHeight+1) == 0 {
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.crossJunction, nil, g.style)
continue
}
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.internalVerticalBorder, nil, g.style)
}
if h%(iCellSizeHeight+1) == 0 {
v.SetContent(x, y+h, g.horizontalRightTJunction, nil, g.style)
v.SetContent(x+width-1, y+h, g.horizontalLeftTJunction, nil, g.style)
continue
}
v.SetContent(x, y+h, g.westBorder, nil, g.style)
v.SetContent(x+width-1, y+h, g.eastBorder, nil, g.style)
}
}
func (g grid) drawFill(v views.View) {
func (g Grid) drawFill(v views.View) {
}
func (g grid) Draw(v views.View) {
func (g Grid) Draw(v views.View) {
g.drawBorders(v)
g.drawFill(v)
}

View file

@ -37,13 +37,13 @@ func (l *layer) draw(s views.View) {
}
}
type unorderedDrawContainer struct {
type UnorderedDrawContainer struct {
id uuid.UUID
contents []Drawable
}
func CreateUnorderedDrawContainer(contents []Drawable) unorderedDrawContainer {
return unorderedDrawContainer{
func CreateUnorderedDrawContainer(contents []Drawable) UnorderedDrawContainer {
return UnorderedDrawContainer{
id: uuid.New(),
contents: contents,
}

46
render/raw.go Normal file
View file

@ -0,0 +1,46 @@
package render
import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type Raw struct {
id uuid.UUID
buffer [][]rune
position util.Position
style tcell.Style
}
func CreateRawDrawable(x, y uint16, style tcell.Style, buffer ...string) *Raw {
r := new(Raw)
r.position = util.PositionAt(x, y)
r.buffer = make([][]rune, 0)
for _, row := range buffer {
r.buffer = append(r.buffer, []rune(row))
}
r.style = style
return r
}
func (r *Raw) UniqueId() uuid.UUID {
return r.id
}
func (r *Raw) Draw(v views.View) {
x := r.position.X()
y := r.position.Y()
for h, row := range r.buffer {
for i, ru := range row {
v.SetContent(x+i, y+h, ru, nil, r.style)
}
}
}

View file

@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
)
type rectangle struct {
type Rectangle struct {
id uuid.UUID
size util.Size
@ -31,7 +31,7 @@ type rectangle struct {
fillRune rune
}
func CreateBorderlessRectangle(x, y uint16, width, height uint16, fillRune rune, style tcell.Style) rectangle {
func CreateBorderlessRectangle(x, y uint16, width, height uint16, fillRune rune, style tcell.Style) Rectangle {
return CreateRectangle(
x, y, width, height,
0, 0, 0,
@ -41,7 +41,7 @@ func CreateBorderlessRectangle(x, y uint16, width, height uint16, fillRune rune,
)
}
func CreateSimpleEmptyRectangle(x, y uint16, width, height uint16, borderRune rune, style tcell.Style) rectangle {
func CreateSimpleEmptyRectangle(x, y uint16, width, height uint16, borderRune rune, style tcell.Style) Rectangle {
return CreateRectangle(
x, y, width, height,
borderRune, borderRune, borderRune,
@ -51,7 +51,7 @@ func CreateSimpleEmptyRectangle(x, y uint16, width, height uint16, borderRune ru
)
}
func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, borderRune rune, fillRune rune, style tcell.Style) rectangle {
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,
@ -61,6 +61,25 @@ func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, bord
)
}
func CreateRectangleV2(
x, y uint16, width, height uint16,
upper, middle, lower string,
isBorderless, isFilled bool,
style tcell.Style,
) Rectangle {
upperRunes := []rune(upper)
middleRunes := []rune(middle)
lowerRunes := []rune(lower)
return CreateRectangle(
x, y, width, height,
upperRunes[0], upperRunes[1], upperRunes[2],
middleRunes[0], middleRunes[1], middleRunes[2],
lowerRunes[0], lowerRunes[1], lowerRunes[2],
isBorderless, isFilled, style,
)
}
// CreateRectangle(
//
// x, y, width, height,
@ -81,8 +100,8 @@ func CreateRectangle(
swCorner, southBorder, seCorner rune,
isBorderless, isFilled bool,
style tcell.Style,
) rectangle {
return rectangle{
) Rectangle {
return Rectangle{
id: uuid.New(),
size: util.SizeOf(width, height),
position: util.PositionAt(x, y),
@ -101,11 +120,15 @@ func CreateRectangle(
}
}
func (rect rectangle) UniqueId() uuid.UUID {
func (rect Rectangle) UniqueId() uuid.UUID {
return rect.id
}
func (rect rectangle) drawBorders(v views.View) {
func (rect Rectangle) Position() util.Position {
return rect.position
}
func (rect Rectangle) drawBorders(v views.View) {
width := rect.size.Width()
height := rect.size.Height()
x := rect.position.X()
@ -116,26 +139,31 @@ func (rect rectangle) drawBorders(v views.View) {
v.SetContent(x, y+height-1, rect.swCorner, nil, rect.style)
v.SetContent(x+width-1, y+height-1, rect.seCorner, nil, rect.style)
for w := range width - 2 {
v.SetContent(1+w, y, rect.northBorder, nil, rect.style)
v.SetContent(1+w, y+height-1, rect.southBorder, nil, rect.style)
for w := 1; w < width-1; w++ {
v.SetContent(x+w, y, rect.northBorder, nil, rect.style)
v.SetContent(x+w, y+height-1, rect.southBorder, nil, rect.style)
}
for h := range height - 2 {
v.SetContent(x, 1+h, rect.westBorder, nil, rect.style)
v.SetContent(x+width-1, 1+h, rect.eastBorder, nil, rect.style)
for h := 1; h < height-1; h++ {
v.SetContent(x, y+h, rect.westBorder, nil, rect.style)
v.SetContent(x+width-1, y+h, rect.eastBorder, nil, rect.style)
}
}
func (rect rectangle) drawFill(v views.View) {
for w := range rect.size.Width() - 2 {
for h := range rect.size.Height() - 2 {
v.SetContent(1+w, 1+h, rect.fillRune, nil, rect.style)
func (rect Rectangle) drawFill(v views.View) {
width := rect.size.Width()
height := rect.size.Height()
x := rect.position.X()
y := rect.position.Y()
for w := 1; w < width-1; w++ {
for h := 1; h < height-1; h++ {
v.SetContent(x+w, y+h, rect.fillRune, nil, rect.style)
}
}
}
func (rect rectangle) Draw(v views.View) {
func (rect Rectangle) Draw(v views.View) {
if !rect.isBorderless {
rect.drawBorders(v)
}

View file

@ -10,7 +10,7 @@ import (
"github.com/google/uuid"
)
type text struct {
type Text struct {
id uuid.UUID
content []string
position util.Position
@ -23,21 +23,43 @@ func CreateText(
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),
}
) *Text {
text := new(Text)
text.id = uuid.New()
text.content = strings.Split(content, " ")
text.style = style
text.size = util.SizeOf(width, height)
text.position = util.PositionAt(x, y)
return text
}
func (t text) UniqueId() uuid.UUID {
func (t *Text) UniqueId() uuid.UUID {
return t.id
}
func (t text) Draw(s views.View) {
func (t *Text) Position() util.Position {
return t.position
}
func (t *Text) Content() string {
return strings.Join(t.content, " ")
}
func (t *Text) Size() util.Size {
return t.size
}
func (t *Text) SetStyle(style tcell.Style) {
t.style = style
}
func (t *Text) Style() tcell.Style {
return t.style
}
func (t *Text) Draw(s views.View) {
width := t.size.Width()
height := t.size.Height()
x := t.position.X()

View file

@ -1 +0,0 @@
package main

62
ui/borderButton.go Normal file
View file

@ -0,0 +1,62 @@
package ui
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UIBorderedButton struct {
id uuid.UUID
text render.Text
border render.Rectangle
isSelected bool
unselectedStyle tcell.Style
selectedStyle tcell.Style
}
func (b *UIBorderedButton) IsSelected() bool {
return b.isSelected
}
func (b *UIBorderedButton) Select() {
b.isSelected = true
}
func (b *UIBorderedButton) Deselect() {
b.isSelected = false
}
func (b *UIBorderedButton) SetSelected(selected bool) {
b.isSelected = selected
}
func (b *UIBorderedButton) UniqueId() uuid.UUID {
return b.id
}
func (b *UIBorderedButton) MoveTo(x uint16, y uint16) {
panic("not implemented") // TODO: Implement
}
func (b *UIBorderedButton) Position() util.Position {
panic("not implemented") // TODO: Implement
}
func (b *UIBorderedButton) Size() util.Size {
panic("not implemented") // TODO: Implement
}
func (b *UIBorderedButton) Draw(v views.View) {
panic("not implemented") // TODO: Implement
}
func (b *UIBorderedButton) Input(e *tcell.EventKey) {
panic("not implemented") // TODO: Implement
}

82
ui/container.go Normal file
View file

@ -0,0 +1,82 @@
package ui
import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UIContainerLayout int
const (
// These change the provided ui positions
UpperLeft UIContainerLayout = iota
MiddleLeft
LowerLeft
UpperRight
MiddleRight
LowerRight
UpperCenter
MiddleCenter
LowerCenter
// This uses the positions as provided in the ui elements
Manual
)
type UIContainer struct {
id uuid.UUID
layout UIContainerLayout
position util.Position
size util.Size
elements []UIElement
}
func CreateUIContainer(x, y uint16, width, height uint16, layout UIContainerLayout) *UIContainer {
container := new(UIContainer)
container.id = uuid.New()
container.layout = layout
container.position = util.PositionAt(x, y)
container.size = util.SizeOf(width, height)
container.elements = make([]UIElement, 0)
return container
}
func (uic *UIContainer) Push(element UIElement) {
uic.elements = append(uic.elements, element)
}
func (uic *UIContainer) Clear() {
uic.elements = make([]UIElement, 0)
}
func (uic *UIContainer) UniqueId() uuid.UUID {
return uic.id
}
func (uic *UIContainer) MoveTo(x, y uint16) {
uic.position = util.PositionAt(x, y)
}
func (uic *UIContainer) Position() util.Position {
return uic.position
}
func (uic *UIContainer) Size() util.Size {
return uic.size
}
func (uic *UIContainer) Draw(v views.View) {
for _, e := range uic.elements {
e.Draw(v)
}
}
func (uic *UIContainer) Input(ev *tcell.EventKey) {
for _, e := range uic.elements {
e.Input(ev)
}
}

56
ui/label.go Normal file
View file

@ -0,0 +1,56 @@
package ui
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/util"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UILabel struct {
id uuid.UUID
text *render.Text
}
func CreateUILabel(x, y uint16, width, height uint16, content string, style tcell.Style) *UILabel {
label := new(UILabel)
label.id = uuid.New()
label.text = render.CreateText(x, y, width, height, content, style)
return label
}
func CreateSingleLineUILabel(x, y uint16, content string, style tcell.Style) *UILabel {
label := new(UILabel)
label.id = uuid.New()
label.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(content)), 1, content, style)
return label
}
func (t *UILabel) UniqueId() uuid.UUID {
return t.id
}
func (t *UILabel) MoveTo(x uint16, y uint16) {
t.text = render.CreateText(x, y, uint16(t.text.Size().Width()), uint16(t.Size().Height()), t.text.Content(), t.text.Style())
}
func (t *UILabel) Position() util.Position {
return t.text.Position()
}
func (t *UILabel) Size() util.Size {
return t.text.Size()
}
func (t *UILabel) Draw(v views.View) {
t.text.Draw(v)
}
func (t *UILabel) Input(e *tcell.EventKey) {}

102
ui/simpleButton.go Normal file
View file

@ -0,0 +1,102 @@
package ui
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/util"
"strings"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UISimpleButton struct {
id uuid.UUID
isHighlighted bool
text *render.Text
selectHandler func()
unhighlightedStyle tcell.Style
highlightedStyle tcell.Style
}
func CreateSimpleButton(x, y uint16, text string, unhighlightedStyle, highlightedStyle tcell.Style, onSelect func()) *UISimpleButton {
sb := new(UISimpleButton)
sb.id = uuid.New()
sb.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(text)), 1, text, unhighlightedStyle)
sb.isHighlighted = false
sb.selectHandler = onSelect
sb.highlightedStyle = highlightedStyle
sb.unhighlightedStyle = unhighlightedStyle
return sb
}
func (sb *UISimpleButton) Select() {
sb.selectHandler()
}
func (sb *UISimpleButton) OnSelect(f func()) {
sb.selectHandler = f
}
func (sb *UISimpleButton) IsHighlighted() bool {
return sb.isHighlighted
}
func (sb *UISimpleButton) Highlight() {
sb.isHighlighted = true
newContent := "[ " + sb.text.Content() + " ]"
sb.text = render.CreateText(
uint16(sb.Position().X()-2), uint16(sb.Position().Y()),
uint16(utf8.RuneCountInString(newContent)), 1,
newContent,
sb.highlightedStyle,
)
}
func (sb *UISimpleButton) Unhighlight() {
sb.isHighlighted = false
content := strings.Trim(sb.text.Content(), " ]")
content = strings.Trim(content, "[ ")
contentLen := utf8.RuneCountInString(content)
sb.text = render.CreateText(
uint16(sb.Position().X()+2), uint16(sb.Position().Y()),
uint16(contentLen), 1,
content,
sb.unhighlightedStyle,
)
}
func (sb *UISimpleButton) SetHighlighted(highlighted bool) {
sb.isHighlighted = highlighted
}
func (sb *UISimpleButton) UniqueId() uuid.UUID {
return sb.id
}
func (sb *UISimpleButton) MoveTo(x uint16, y uint16) {
sb.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(sb.text.Content())), 1, sb.text.Content(), sb.highlightedStyle)
}
func (sb *UISimpleButton) Position() util.Position {
return sb.text.Position()
}
func (sb *UISimpleButton) Size() util.Size {
return sb.text.Size()
}
func (sb *UISimpleButton) Draw(v views.View) {
sb.text.Draw(v)
}
func (sb *UISimpleButton) Input(e *tcell.EventKey) {
}

View file

@ -3,10 +3,30 @@ package ui
import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UIElement interface {
UniqueId() uuid.UUID
MoveTo(x, y uint16)
Position() util.Position
Size() util.Size
Draw(v views.View)
Input(e *tcell.EventKey)
}
type UIHighlightableElement interface {
IsHighlighted() bool
Highlight()
Unhighlight()
SetHighlighted(highlighted bool)
UIElement
}
type UISelectableElement interface {
Select()
OnSelect(func())
UIHighlightableElement
}

62
ui/window.go Normal file
View file

@ -0,0 +1,62 @@
package ui
import (
"mvvasilev/last_light/render"
"mvvasilev/last_light/util"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
"github.com/google/uuid"
)
type UIWindow struct {
id uuid.UUID
title *render.Text
box render.Rectangle
}
func CreateWindow(x, y, width, height uint16, title string, style tcell.Style) *UIWindow {
w := new(UIWindow)
titleLen := utf8.RuneCountInString(title)
titlePos := (width / 2) - uint16(titleLen/2)
w.title = render.CreateText(x+titlePos, y, uint16(titleLen), 1, title, style)
w.box = render.CreateRectangle(
x, y, width, height,
'┌', '─', '┐',
'│', ' ', '│',
'└', '─', '┘',
false, true, style,
)
return w
}
func (w *UIWindow) UniqueId() uuid.UUID {
return w.id
}
func (w *UIWindow) MoveTo(x uint16, y uint16) {
}
func (w *UIWindow) Position() util.Position {
return w.box.Position()
}
func (w *UIWindow) Size() util.Size {
return util.SizeOf(0, 0)
}
func (w *UIWindow) Draw(v views.View) {
w.box.Draw(v)
w.title.Draw(v)
}
func (w *UIWindow) Input(e *tcell.EventKey) {
}

View file

@ -17,6 +17,14 @@ func (p Position) Y() int {
return p.y
}
func (p Position) XY() (int, int) {
return p.x, p.y
}
func (p Position) XYUint16() (uint16, uint16) {
return uint16(p.x), uint16(p.y)
}
type Size struct {
width int
height int
@ -33,3 +41,23 @@ func (s Size) Width() int {
func (s Size) Height() int {
return s.height
}
func (s Size) WHUint16() (uint16, uint16) {
return uint16(s.width), uint16(s.height)
}
func LimitIncrement(i int, limit int) int {
if (i + 1) > limit {
return i
}
return i + 1
}
func LimitDecrement(i int, limit int) int {
if (i - 1) < limit {
return i
}
return i - 1
}