224 lines
4.5 KiB
Go
224 lines
4.5 KiB
Go
package hud
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/text/v2"
|
|
"golang.org/x/image/font/basicfont"
|
|
|
|
"github.com/atridad/BigFeelings/internal/status"
|
|
)
|
|
|
|
var (
|
|
basicFace = text.NewGoXFace(basicfont.Face7x13)
|
|
basicFaceAscent = basicFace.Metrics().HAscent
|
|
rectPixel = newRectPixel()
|
|
)
|
|
|
|
func newRectPixel() *ebiten.Image {
|
|
img := ebiten.NewImage(1, 1)
|
|
img.Fill(color.White)
|
|
return img
|
|
}
|
|
|
|
func drawHUDText(screen *ebiten.Image, txt string, clr color.Color, x, y int) {
|
|
if clr == nil {
|
|
clr = color.White
|
|
}
|
|
op := &text.DrawOptions{}
|
|
op.GeoM.Translate(float64(x), float64(y)-basicFaceAscent)
|
|
op.ColorScale.ScaleWithColor(clr)
|
|
text.Draw(screen, txt, basicFace, op)
|
|
}
|
|
|
|
// Drawable HUD chunk.
|
|
type Element interface {
|
|
Draw(screen *ebiten.Image, x, y int) (width, height int)
|
|
}
|
|
|
|
// Plain text node.
|
|
type Label struct {
|
|
Text string
|
|
Color color.Color
|
|
}
|
|
|
|
func (l Label) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
if l.Color == nil {
|
|
l.Color = color.White
|
|
}
|
|
drawHUDText(screen, l.Text, l.Color, x, y)
|
|
width := len(l.Text) * 7 // approximate width for basicfont
|
|
return width, 13
|
|
}
|
|
|
|
// Percent readout node.
|
|
type PercentageDisplay struct {
|
|
Meter status.Meter
|
|
Color color.Color
|
|
}
|
|
|
|
func (p PercentageDisplay) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
if p.Color == nil {
|
|
p.Color = color.White
|
|
}
|
|
txt := fmt.Sprintf("%3.0f%%", p.Meter.Level)
|
|
drawHUDText(screen, txt, p.Color, x, y)
|
|
return len(txt) * 7, 13
|
|
}
|
|
|
|
// Combined label and percent.
|
|
type MeterLabel struct {
|
|
Meter status.Meter
|
|
Color color.Color
|
|
}
|
|
|
|
func (m MeterLabel) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
if m.Color == nil {
|
|
m.Color = color.White
|
|
}
|
|
var txt string
|
|
if m.Meter.Base < 0 {
|
|
// Text-only display without percentage.
|
|
txt = m.Meter.Label
|
|
} else {
|
|
// Standard meter with percentage.
|
|
txt = fmt.Sprintf("%s: %3.0f%%", m.Meter.Label, m.Meter.Level)
|
|
}
|
|
drawHUDText(screen, txt, m.Meter.Color, x, y)
|
|
return len(txt) * 7, 13
|
|
}
|
|
|
|
// Horizontal meter bar.
|
|
type Bar struct {
|
|
Meter status.Meter
|
|
MaxWidth int
|
|
Height int
|
|
BorderColor color.Color
|
|
ShowBorder bool
|
|
}
|
|
|
|
func (b Bar) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
maxWidth := b.MaxWidth
|
|
if maxWidth <= 0 {
|
|
maxWidth = 180
|
|
}
|
|
height := b.Height
|
|
if height <= 0 {
|
|
height = 8
|
|
}
|
|
|
|
fillWidth := int((b.Meter.Level / 100.0) * float64(maxWidth))
|
|
if fillWidth < 0 {
|
|
fillWidth = 0
|
|
}
|
|
if fillWidth > maxWidth {
|
|
fillWidth = maxWidth
|
|
}
|
|
|
|
// Draw border if requested
|
|
if b.ShowBorder {
|
|
borderColor := b.BorderColor
|
|
if borderColor == nil {
|
|
borderColor = color.RGBA{R: 80, G: 80, B: 80, A: 255}
|
|
}
|
|
// Top border
|
|
drawRect(screen, x, y, maxWidth, 1, borderColor)
|
|
// Bottom border
|
|
drawRect(screen, x, y+height-1, maxWidth, 1, borderColor)
|
|
// Left border
|
|
drawRect(screen, x, y, 1, height, borderColor)
|
|
// Right border
|
|
drawRect(screen, x+maxWidth-1, y, 1, height, borderColor)
|
|
}
|
|
|
|
// Draw filled portion
|
|
if fillWidth > 0 {
|
|
drawRect(screen, x, y, fillWidth, height, b.Meter.Color)
|
|
}
|
|
|
|
return maxWidth, height
|
|
}
|
|
|
|
// Helper for filled rectangles.
|
|
func drawRect(screen *ebiten.Image, x, y, width, height int, clr color.Color) {
|
|
if width <= 0 || height <= 0 {
|
|
return
|
|
}
|
|
if clr == nil {
|
|
clr = color.White
|
|
}
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Scale(float64(width), float64(height))
|
|
op.GeoM.Translate(float64(x), float64(y))
|
|
op.ColorScale.ScaleWithColor(clr)
|
|
|
|
screen.DrawImage(rectPixel, op)
|
|
}
|
|
|
|
// Empty padding block.
|
|
type Spacer struct {
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
func (s Spacer) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
return s.Width, s.Height
|
|
}
|
|
|
|
// Horizontal layout row.
|
|
type Row struct {
|
|
Elements []Element
|
|
Spacing int
|
|
}
|
|
|
|
func (r Row) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
totalWidth := 0
|
|
maxHeight := 0
|
|
currentX := x
|
|
|
|
for i, elem := range r.Elements {
|
|
w, h := elem.Draw(screen, currentX, y)
|
|
totalWidth += w
|
|
if h > maxHeight {
|
|
maxHeight = h
|
|
}
|
|
currentX += w
|
|
if i < len(r.Elements)-1 {
|
|
currentX += r.Spacing
|
|
totalWidth += r.Spacing
|
|
}
|
|
}
|
|
|
|
return totalWidth, maxHeight
|
|
}
|
|
|
|
// Vertical stack layout.
|
|
type Column struct {
|
|
Elements []Element
|
|
Spacing int
|
|
}
|
|
|
|
func (c Column) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|
maxWidth := 0
|
|
totalHeight := 0
|
|
currentY := y
|
|
|
|
for i, elem := range c.Elements {
|
|
w, h := elem.Draw(screen, x, currentY)
|
|
if w > maxWidth {
|
|
maxWidth = w
|
|
}
|
|
totalHeight += h
|
|
currentY += h
|
|
if i < len(c.Elements)-1 {
|
|
currentY += c.Spacing
|
|
totalHeight += c.Spacing
|
|
}
|
|
}
|
|
|
|
return maxWidth, totalHeight
|
|
}
|