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/LilGuy/internal/status" ) var ( basicFace = text.NewGoXFace(basicfont.Face7x13) basicFaceAscent = basicFace.Metrics().HAscent rectPixel = newRectPixel() ) // Drawing helpers 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) } // HUD elements type Element interface { Draw(screen *ebiten.Image, x, y int) (width, height int) } 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 } 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 } 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 { txt = m.Meter.Label } else { 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 } 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 dark background drawRect(screen, x, y, maxWidth, height, color.RGBA{R: 30, G: 30, B: 30, A: 255}) // Draw filled portion if fillWidth > 0 { drawRect(screen, x, y, fillWidth, height, b.Meter.Color) } // Draw border borderColor := b.BorderColor if borderColor == nil { borderColor = color.RGBA{R: 180, G: 180, B: 180, A: 255} } drawRect(screen, x, y, maxWidth, 1, borderColor) drawRect(screen, x, y+height-1, maxWidth, 1, borderColor) drawRect(screen, x, y, 1, height, borderColor) drawRect(screen, x+maxWidth-1, y, 1, height, borderColor) return maxWidth, height } // Rectangle drawing 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) } // Layout elements type Spacer struct { Width int Height int } func (s Spacer) Draw(screen *ebiten.Image, x, y int) (int, int) { return s.Width, s.Height } 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 } 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 }