feat(ui): tabbar within leftbar

This commit is contained in:
Muhammad Nauman Raza 2023-07-23 15:27:31 +01:00
parent 21f0e8a429
commit 888bcb0404
5 changed files with 204 additions and 63 deletions

View file

@ -4,7 +4,9 @@ go 1.20
require ( require (
github.com/ebitenui/ebitenui v0.5.4 github.com/ebitenui/ebitenui v0.5.4
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/hajimehoshi/ebiten/v2 v2.5.5 github.com/hajimehoshi/ebiten/v2 v2.5.5
golang.org/x/image v0.7.0
) )
require ( require (
@ -12,7 +14,6 @@ require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/jezek/xgb v1.1.0 // indirect github.com/jezek/xgb v1.1.0 // indirect
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect
golang.org/x/sync v0.2.0 // indirect golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect

View file

@ -7,6 +7,7 @@ github.com/ebitenui/ebitenui v0.5.4/go.mod h1:2iyyjninLWNQLZ+pdV/eJ9vRnSs+I+HrzE
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hajimehoshi/bitmapfont/v2 v2.2.3 h1:jmq/TMNj352V062Tr5e3hAoipkoxCbY1JWTzor0zNps= github.com/hajimehoshi/bitmapfont/v2 v2.2.3 h1:jmq/TMNj352V062Tr5e3hAoipkoxCbY1JWTzor0zNps=
github.com/hajimehoshi/ebiten/v2 v2.5.5 h1:TJNoZsYJYUyFucwE56QRSgmZ+/cklUt1YrwpQVC5vjs= github.com/hajimehoshi/ebiten/v2 v2.5.5 h1:TJNoZsYJYUyFucwE56QRSgmZ+/cklUt1YrwpQVC5vjs=
github.com/hajimehoshi/ebiten/v2 v2.5.5/go.mod h1:mnHSOVysTr/nUZrN1lBTRqhK4NG+T9NR3JsJP2rCppk= github.com/hajimehoshi/ebiten/v2 v2.5.5/go.mod h1:mnHSOVysTr/nUZrN1lBTRqhK4NG+T9NR3JsJP2rCppk=

View file

@ -6,23 +6,22 @@ import (
// Logs // Logs
"log" "log"
"strconv"
// Ebitengine // Ebitengine
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil" // "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
// Create the `Game` struct // Create the `Game` struct
type Game struct { type Game struct {
ui UI ui UI
activePlayer Player activePlayer Player
} }
// Define the window width/height
const ( const (
window_width = 640 window_width = 1440
window_height = 480 window_height = 960
) )
// Update implements Game // Update implements Game
@ -36,11 +35,6 @@ func (g *Game) Update() error {
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {
// Draw the UI onto the screen // Draw the UI onto the screen
g.ui.base.Draw(screen) g.ui.base.Draw(screen)
ebitenutil.DebugPrintAt(screen, "health: " + strconv.Itoa(g.activePlayer.health), 0, 0)
ebitenutil.DebugPrintAt(screen, "level: " + strconv.Itoa(g.activePlayer.level), 0, 10)
ebitenutil.DebugPrintAt(screen, "exp: " + strconv.Itoa(int(g.activePlayer.exp)), 0, 20)
ebitenutil.DebugPrintAt(screen, "ambittion: " + strconv.Itoa(int(g.activePlayer.ambition)), 0, 30)
} }
// Layout implements Game // Layout implements Game
@ -63,13 +57,14 @@ func main() {
// Engine setup // Engine setup
ebiten.SetWindowSize(window_width, window_height) ebiten.SetWindowSize(window_width, window_height)
ebiten.SetWindowTitle(window_title) ebiten.SetWindowTitle(window_title)
// Initialise the test player
testPlayer := initPlayer() testPlayer := initPlayer()
// Initialise the game // Initialise the game
game := Game{ game := Game{
// Initialise the UI // Initialise the UI
ui: uiInit(window_width, window_height), ui: uiInit(window_width, window_height),
activePlayer: testPlayer, activePlayer: testPlayer,
} }

View file

@ -1,43 +1,76 @@
package main package main
import ( import ()
"math/rand"
)
// The player struct
type Player struct { type Player struct {
health int health int
level int defense int
exp float32 level int
ambition float32 exp float32
ambition float32
ambition_max float32
} }
// NOTE(midnadimple): These gates are temporary. We'll decide on real values later // Create the maps for the level/(max) ambition gates
var level_gates = map[int]float32{ var level_gates = make(map[int]float32)
1: 100.0, var level_ambition = make(map[int]float32)
2: 150.0,
3: 300.0,
}
// TODO(midnadimple): Move player initialization to server upon login // TODO(midnadimple): Move player initialization to server upon login
func initPlayer() Player { func initPlayer() Player {
return Player{ return Player{
health: 100, health: 100,
level: 1, level: 1,
exp: 0.0, exp: 0.0,
ambition: (rand.Float32() * 10), // NOTE(midnadimple): In the future this will be affected by player activity ambition: 0.0, // NOTE(midnadimple): In the future this will be affected by player activity
ambition_max: level_ambition[1], // NOTE(midnadimple): In the future this will be affected by player activity
} }
} }
// Formula for XP gain - extremely simple
func gain(basexp float32, modifier float32) float32 {
gain := basexp * modifier
return gain
}
// Update the player
func (p *Player) update() { func (p *Player) update() {
// TODO(midnadimple): update health upon damage // TODO(midnadimple): update health upon damage
// Auto-generate the level gates
level_gates[1] = 500
for i := 0; i <= 1000; i++ {
if i >= 2 {
switch {
case i <= 10:
level_gates[i] = level_gates[i-1] * 1.2
case (i <= 100) && (i > 10):
level_gates[i] = level_gates[i-1] * 1.1
case (i <= 1000) && (i > 100):
level_gates[i] = level_gates[i-1] * 1.005
}
}
}
// Auto-generate maximum ambition gates
level_ambition[1] = 10
for i := 0; i <= 1000; i++ {
if i >= 2 {
switch {
case i <= 10:
level_ambition[i] = level_ambition[i-1] * 1.1
case (i <= 100) && (i > 10):
level_ambition[i] = level_ambition[i-1] * 1.05
case (i <= 1000) && (i > 100):
level_ambition[i] = level_ambition[i-1] * 1.00005
}
}
}
// Set the XP to 0 and increase both the level and max ambition on level up
if p.exp >= level_gates[p.level] { if p.exp >= level_gates[p.level] {
p.exp = 0.0 p.exp = 0.0
p.level += 1 p.level += 1
p.ambition_max = level_ambition[p.level]
} }
}
// NOTE(midnadimple): This formula for exp gain is pretty simple, maybe devraza can think of
// a more practical one
p.exp += 10.0 * (1.0/60.0) * p.ambition
}

View file

@ -5,45 +5,57 @@ import (
img "image" img "image"
"image/color" "image/color"
// String convert
"strconv"
// EbitenUI // EbitenUI
"github.com/ebitenui/ebitenui" "github.com/ebitenui/ebitenui"
"github.com/ebitenui/ebitenui/image" "github.com/ebitenui/ebitenui/image"
"github.com/ebitenui/ebitenui/widget" "github.com/ebitenui/ebitenui/widget"
// Fonts
"github.com/devraza/ambition/assets/fonts"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
) )
type UI struct { type UI struct {
base ebitenui.UI base ebitenui.UI
colors map[string]color.RGBA colors map[string]color.RGBA
width, height int width, height int
} }
// The `hazakura` colorscheme (default)
var hazakura = map[string]color.RGBA{
// The monotone colors
"dark_black": color.RGBA{0x0f, 0x0f, 0x0d, 0xff},
"black": color.RGBA{0x15, 0x15, 0x17, 0xff},
"dark_gray": color.RGBA{0x24, 0x24, 0x26, 0xff},
"gray": color.RGBA{0x27, 0x27, 0x2b, 0xff},
"light_gray": color.RGBA{0x45, 0x44, 0x49, 0xff},
"overlay": color.RGBA{0x5c, 0x5c, 0x61, 0xff},
"highlight": color.RGBA{0xd9, 0x0, 0xd7, 0xff},
"subwhite": color.RGBA{0xec, 0xe5, 0xea, 0xff},
// Actual* colors
"red": color.RGBA{0xf0, 0x69, 0x69, 0xff},
"magenta": color.RGBA{0xe8, 0x87, 0xbb, 0xff},
"purple": color.RGBA{0xa2, 0x92, 0xe8, 0xff},
"blue": color.RGBA{0x78, 0xb9, 0xc4, 0xff},
"cyan": color.RGBA{0x7e, 0xe6, 0xae, 0xff},
"green": color.RGBA{0x91, 0xd6, 0x5c, 0xff},
"yellow": color.RGBA{0xd9, 0xd5, 0x64, 0xff},
}
// Function for UI initialization // Function for UI initialization
func uiInit(width, height int) UI { func uiInit(width, height int) UI {
var ui UI var ui UI
// The `hazakura` colorscheme
{
hazakura := make(map[string]color.RGBA)
// The monotone colors
hazakura["dark_black"] = color.RGBA{0x0f, 0x0f, 0x0d, 0xff}
hazakura["black"] = color.RGBA{0x15, 0x15, 0x17, 0xff}
hazakura["dark_gray"] = color.RGBA{0x24, 0x24, 0x26, 0xff}
hazakura["gray"] = color.RGBA{0x27, 0x27, 0x2b, 0xff}
hazakura["light_gray"] = color.RGBA{0x45, 0x44, 0x49, 0xff}
hazakura["overlay"] = color.RGBA{0x5c, 0x5c, 0x61, 0xff}
hazakura["highlight"] = color.RGBA{0xd9, 0x0, 0xd7, 0xff}
hazakura["subwhite"] = color.RGBA{0xec, 0xe5, 0xea, 0xff}
// Actual* colors // Define the UI colors
hazakura["red"] = color.RGBA{0xf0, 0x69, 0x69, 0xff} ui.colors = hazakura
hazakura["magenta"] = color.RGBA{0xe8, 0x87, 0xbb, 0xff}
hazakura["purple"] = color.RGBA{0xa2, 0x92, 0xe8, 0xff}
hazakura["blue"] = color.RGBA{0x78, 0xb9, 0xc4, 0xff}
hazakura["cyan"] = color.RGBA{0x7e, 0xe6, 0xae, 0xff}
hazakura["green"] = color.RGBA{0x91, 0xd6, 0x5c, 0xff}
hazakura["yellow"] = color.RGBA{0xd9, 0xd5, 0x64, 0xff}
ui.colors = hazakura // Load the images for the button states
} buttonImage, _ := loadButtonImage()
// Get the window width/height // Get the window width/height
ui.width = width ui.width = width
@ -55,12 +67,82 @@ func uiInit(width, height int) UI {
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(ui.colors["dark_black"])), widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(ui.colors["dark_black"])),
) )
// Load the text font
face, _ := loadFont(10)
tabProfile := widget.NewTabBookTab("Profile",
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(ui.colors["gray"])),
widget.ContainerOpts.Layout(widget.NewGridLayout(
//Define number of columns in the grid
widget.GridLayoutOpts.Columns(2),
widget.GridLayoutOpts.Spacing(10, 10),
//specify the Stretch for each row and column.
widget.GridLayoutOpts.Stretch([]bool{false, true}, nil),
)),
)
// TODO(devraza): Show all of the player's stats
// health := widget.NewButton(
// widget.ButtonOpts.Text(strconv.Itoa(activePlayer.health), face, buttonTextColor),
// )
tabProfile.AddChild(health)
tabInventory := widget.NewTabBookTab("Inventory",
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0, 255, 0, 0xff})),
widget.ContainerOpts.Layout(widget.NewAnchorLayout()),
)
inventoryButton := widget.NewText(
widget.TextOpts.Text("Inventory content", face, color.Black),
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
widget.TextOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
HorizontalPosition: widget.AnchorLayoutPositionCenter,
VerticalPosition: widget.AnchorLayoutPositionCenter,
})),
)
tabInventory.AddChild(inventoryButton)
tabOther := widget.NewTabBookTab("Other",
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0, 255, 0, 0xff})),
widget.ContainerOpts.Layout(widget.NewAnchorLayout()),
)
otherButton := widget.NewText(
widget.TextOpts.Text("Other content", face, color.Black),
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
widget.TextOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
HorizontalPosition: widget.AnchorLayoutPositionCenter,
VerticalPosition: widget.AnchorLayoutPositionCenter,
})),
)
tabOther.AddChild(otherButton)
// Create the tabbook widget
leftTabs := widget.NewTabBook(
widget.TabBookOpts.TabButtonImage(buttonImage),
widget.TabBookOpts.TabButtonText(face, &widget.ButtonTextColor{Idle: color.White}),
widget.TabBookOpts.TabButtonSpacing(0),
widget.TabBookOpts.ContainerOpts(
widget.ContainerOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
StretchHorizontal: true,
StretchVertical: true,
HorizontalPosition: widget.AnchorLayoutPositionCenter,
VerticalPosition: widget.AnchorLayoutPositionCenter,
}),
),
),
widget.TabBookOpts.TabButtonOpts(
widget.ButtonOpts.TextPadding(widget.NewInsetsSimple(5)),
widget.ButtonOpts.WidgetOpts(widget.WidgetOpts.MinSize(int(float32(width)/(3.5*3)), 0)),
),
widget.TabBookOpts.Tabs(tabProfile, tabInventory, tabOther),
)
// Define the left bar container // Define the left bar container
leftBar := widget.NewContainer( leftBar := widget.NewContainer(
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(ui.colors["gray"])), widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(ui.colors["gray"])),
) )
// Add the tabbook to the left bar
leftBar.AddChild(leftTabs)
// Set the position and size of the left bar // Set the position and size of the left bar
leftBar.SetLocation(img.Rect(0, 0, int(float64(width)/3.5), height)) leftBar.SetLocation(img.Rect(0, 0, int(float32(width)/3.5), height))
// Add the left bar to the root container // Add the left bar to the root container
root.AddChild(leftBar) root.AddChild(leftBar)
@ -68,3 +150,32 @@ func uiInit(width, height int) UI {
return ui return ui
} }
func loadButtonImage() (*widget.ButtonImage, error) {
idle := image.NewNineSliceColor(hazakura["black"])
hover := image.NewNineSliceColor(hazakura["light_gray"])
pressed := image.NewNineSliceColor(hazakura["gray"])
pressedHover := image.NewNineSliceColor(hazakura["gray"])
disabled := image.NewNineSliceColor(hazakura["overlay"])
return &widget.ButtonImage{
Idle: idle,
Hover: hover,
Pressed: pressed,
PressedHover: pressedHover,
Disabled: disabled,
}, nil
}
func loadFont(size float64) (font.Face, error) {
ttfFont, err := truetype.Parse(fonts.IosevkaBold_ttf)
if err != nil {
return nil, err
}
return truetype.NewFace(ttfFont, &truetype.Options{
Size: size,
DPI: 72,
Hinting: font.HintingFull,
}), nil
}