citylimits/system/renderhud.go

461 lines
13 KiB
Go
Raw Normal View History

2022-01-11 04:05:30 +00:00
package system
import (
"image"
"image/color"
2022-01-23 00:21:02 +00:00
"math"
2022-01-19 23:32:02 +00:00
"strings"
2022-01-11 04:05:30 +00:00
2022-01-14 05:30:52 +00:00
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
2022-01-11 04:05:30 +00:00
"code.rocketnine.space/tslocum/citylimits/component"
"code.rocketnine.space/tslocum/citylimits/world"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
)
2022-01-23 00:21:02 +00:00
const (
helpW = 480
helpH = 185
)
2022-01-11 04:05:30 +00:00
type RenderHudSystem struct {
2022-01-14 05:30:52 +00:00
op *ebiten.DrawImageOptions
hudImg *ebiten.Image
tmpImg *ebiten.Image
2022-01-15 00:58:44 +00:00
tmpImg2 *ebiten.Image
2022-01-23 00:21:02 +00:00
helpImg *ebiten.Image
2022-01-14 05:30:52 +00:00
sidebarColor color.RGBA
2022-01-11 04:05:30 +00:00
}
func NewRenderHudSystem() *RenderHudSystem {
s := &RenderHudSystem{
2022-01-15 00:58:44 +00:00
op: &ebiten.DrawImageOptions{},
hudImg: ebiten.NewImage(1, 1),
tmpImg: ebiten.NewImage(1, 1),
tmpImg2: ebiten.NewImage(1, 1),
2022-01-23 00:21:02 +00:00
helpImg: ebiten.NewImage(helpW, helpH),
2022-01-11 04:05:30 +00:00
}
2022-01-14 05:30:52 +00:00
sidebarShade := uint8(111)
s.sidebarColor = color.RGBA{sidebarShade, sidebarShade, sidebarShade, 255}
2022-01-11 04:05:30 +00:00
return s
}
func (s *RenderHudSystem) Needs() []gohan.ComponentID {
return []gohan.ComponentID{
component.PositionComponentID,
component.VelocityComponentID,
component.WeaponComponentID,
}
}
func (s *RenderHudSystem) Uses() []gohan.ComponentID {
return nil
}
func (s *RenderHudSystem) Update(_ *gohan.Context) error {
return nil
}
func (s *RenderHudSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error {
// Draw HUD.
if world.World.HUDUpdated {
2022-01-14 07:14:22 +00:00
s.hudImg.Clear()
s.drawSidebar()
s.drawMessages()
2022-01-14 07:14:22 +00:00
s.drawTooltip()
2022-01-23 00:21:02 +00:00
s.drawHelp()
2022-01-11 04:05:30 +00:00
world.World.HUDUpdated = false
}
screen.DrawImage(s.hudImg, nil)
return nil
}
2022-01-23 00:21:02 +00:00
const columns = 3
const buttonWidth = world.SidebarWidth / columns
2022-01-14 07:14:22 +00:00
func (s *RenderHudSystem) drawSidebar() {
2022-01-11 04:05:30 +00:00
bounds := s.hudImg.Bounds()
if bounds.Dx() != world.World.ScreenW || bounds.Dy() != world.World.ScreenH {
s.hudImg = ebiten.NewImage(world.World.ScreenW, world.World.ScreenH)
s.tmpImg = ebiten.NewImage(world.World.ScreenW, world.World.ScreenH)
2022-01-15 00:58:44 +00:00
s.tmpImg2 = ebiten.NewImage(world.SidebarWidth, world.World.ScreenH)
2022-01-11 04:05:30 +00:00
} else {
s.hudImg.Clear()
s.tmpImg.Clear()
2022-01-15 00:58:44 +00:00
s.tmpImg2.Clear()
2022-01-11 04:05:30 +00:00
}
2022-01-14 05:30:52 +00:00
// Fill background.
2022-01-15 00:58:44 +00:00
s.hudImg.SubImage(image.Rect(0, 0, world.SidebarWidth, world.World.ScreenH)).(*ebiten.Image).Fill(s.sidebarColor)
2022-01-14 05:30:52 +00:00
// Draw buttons.
2022-01-11 04:05:30 +00:00
2022-01-15 00:58:44 +00:00
const paddingSize = 1
const buttonHeight = buttonWidth
2022-01-11 04:05:30 +00:00
world.World.HUDButtonRects = make([]image.Rectangle, len(world.HUDButtons))
2022-01-14 05:30:52 +00:00
var lastButtonY int
2022-01-11 04:05:30 +00:00
for i, button := range world.HUDButtons {
row := i / columns
2022-01-14 05:30:52 +00:00
x, y := (i%columns)*buttonWidth, row*buttonHeight
r := image.Rect(x+paddingSize, y+paddingSize, x+buttonWidth-paddingSize, y+buttonHeight-paddingSize)
2022-01-19 23:32:02 +00:00
if button != nil {
selected := world.World.HoverStructure == button.StructureType
if button.StructureType == world.StructureToggleTransparentStructures {
selected = world.World.TransparentStructures
}
2022-01-11 04:05:30 +00:00
2022-01-19 23:32:02 +00:00
// Draw background.
s.drawButtonBackground(s.tmpImg, r, selected)
2022-01-11 04:05:30 +00:00
2022-01-19 23:32:02 +00:00
// Draw sprite.
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(x+paddingSize)+button.SpriteOffsetX, float64(y+paddingSize)+button.SpriteOffsetY)
s.tmpImg.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).DrawImage(button.Sprite, op)
2022-01-11 22:22:40 +00:00
2022-01-19 23:32:02 +00:00
s.drawButtonBorder(s.tmpImg, r, selected)
}
2022-01-11 22:22:40 +00:00
world.World.HUDButtonRects[i] = r
2022-01-23 00:21:02 +00:00
if button != nil && button.StructureType != world.StructureToggleTransparentStructures {
lastButtonY = y
}
2022-01-11 04:05:30 +00:00
}
2022-01-14 05:30:52 +00:00
2022-01-23 00:21:02 +00:00
dateY := lastButtonY + buttonHeight*2 - buttonHeight/2 - 16
s.drawDate(dateY)
s.drawFunds(dateY + 50)
2022-01-14 05:30:52 +00:00
2022-01-23 00:21:02 +00:00
indicatorY := dateY + 179
// Draw RCI indicator.
s.drawDemand(buttonWidth/2, indicatorY)
2022-01-14 05:30:52 +00:00
2022-01-23 00:21:02 +00:00
// Draw PWR indicator.
s.drawPower(buttonWidth/2+buttonWidth, indicatorY)
2022-01-11 04:05:30 +00:00
s.hudImg.DrawImage(s.tmpImg, nil)
2022-01-23 00:21:02 +00:00
s.hudImg.SubImage(image.Rect(world.SidebarWidth-1, 0, world.SidebarWidth, world.World.ScreenH)).(*ebiten.Image).Fill(color.Black)
2022-01-11 04:05:30 +00:00
}
2022-01-14 05:30:52 +00:00
func (s *RenderHudSystem) drawButtonBackground(img *ebiten.Image, r image.Rectangle, selected bool) {
buttonShade := uint8(142)
colorButton := color.RGBA{buttonShade, buttonShade, buttonShade, 255}
bgColor := colorButton
if selected {
bgColor = s.sidebarColor
}
img.SubImage(r).(*ebiten.Image).Fill(bgColor)
}
func (s *RenderHudSystem) drawButtonBorder(img *ebiten.Image, r image.Rectangle, selected bool) {
borderSize := 2
lightBorderShade := uint8(216)
colorLightBorder := color.RGBA{lightBorderShade, lightBorderShade, lightBorderShade, 255}
mediumBorderShade := uint8(56)
colorMediumBorder := color.RGBA{mediumBorderShade, mediumBorderShade, mediumBorderShade, 255}
darkBorderShade := uint8(42)
colorDarkBorder := color.RGBA{darkBorderShade, darkBorderShade, darkBorderShade, 255}
topLeftBorder := colorLightBorder
bottomRightBorder := colorMediumBorder
if selected {
topLeftBorder = colorDarkBorder
bottomRightBorder = colorLightBorder
}
// Draw top and left border.
img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+borderSize)).(*ebiten.Image).Fill(topLeftBorder)
img.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+borderSize, r.Max.Y)).(*ebiten.Image).Fill(topLeftBorder)
// Draw bottom and right border.
img.SubImage(image.Rect(r.Min.X, r.Max.Y-borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(bottomRightBorder)
img.SubImage(image.Rect(r.Max.X-borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(bottomRightBorder)
}
2022-01-14 07:14:22 +00:00
func (s *RenderHudSystem) drawTooltip() {
label := world.Tooltip()
if label == "" {
return
}
2022-01-19 23:32:02 +00:00
lines := 1 + strings.Count(label, "\n")
max := maxLen(strings.Split(label, "\n"))
2022-01-19 23:32:02 +00:00
2022-01-14 07:14:22 +00:00
scale := 3.0
x, y := world.SidebarWidth, 0
w, h := (max*6+10)*int(scale), 16*(int(scale))*lines+10
r := image.Rect(x, y, x+w, y+h)
s.hudImg.SubImage(r).(*ebiten.Image).Fill(color.RGBA{0, 0, 0, 120})
s.tmpImg.Clear()
ebitenutil.DebugPrint(s.tmpImg, label)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(world.SidebarWidth+(4*scale), 2)
s.hudImg.DrawImage(s.tmpImg, op)
}
func maxLen(v []string) int {
max := 0
for _, line := range v {
l := len(line)
if l > max {
max = l
}
}
return max
}
2022-01-23 00:21:02 +00:00
func (s *RenderHudSystem) drawDemand(x, y int) {
const rciSize = 100
rciX := x
rciY := y
const rciButtonHeight = 20
colorR := color.RGBA{0, 255, 0, 255}
colorC := color.RGBA{0, 0, 255, 255}
colorI := color.RGBA{231, 231, 72, 255}
demandR, demandC, demandI := world.Demand()
drawDemandBar := func(demand float64, clr color.RGBA, i int) {
barOffsetSize := 12
barOffset := -barOffsetSize + (i * barOffsetSize)
barWidth := 7
barX := rciX + buttonWidth/2 - barWidth/2 + barOffset
barY := rciY + (rciSize / 2)
if demand < 0 {
barY += rciButtonHeight / 2
} else {
barY -= rciButtonHeight / 2
}
barHeight := int((float64(rciSize) / 2) * demand)
s.tmpImg.SubImage(image.Rect(barX, barY, barX+barWidth, barY-barHeight)).(*ebiten.Image).Fill(clr)
}
drawDemandBar(demandR, colorR, 0)
drawDemandBar(demandC, colorC, 1)
drawDemandBar(demandI, colorI, 2)
// Draw button.
const rciButtonPadding = 12
const rciButtonLabelPaddingX = 6
const rciButtonLabelPaddingY = 1
rciButtonY := rciY + (rciSize / 2) - (rciButtonHeight / 2)
rciButtonRect := image.Rect(rciX+rciButtonPadding, rciButtonY, rciX+buttonWidth-rciButtonPadding, rciButtonY+rciButtonHeight)
s.drawButtonBackground(s.tmpImg, rciButtonRect, false) // TODO
// Draw label.
ebitenutil.DebugPrintAt(s.tmpImg, "R C I", rciX+rciButtonPadding+rciButtonLabelPaddingX, rciButtonY+rciButtonLabelPaddingY)
s.drawButtonBorder(s.tmpImg, rciButtonRect, false) // TODO
}
func (s *RenderHudSystem) drawPower(x, y int) {
const rciSize = 100
rciX := x
rciY := y
const rciButtonHeight = 20
colorPowerNormal := color.RGBA{0, 255, 0, 255}
colorPowerOut := color.RGBA{255, 0, 0, 255}
colorPowerCapacity := color.RGBA{16, 16, 16, 255}
drawPowerBar := func(demand float64, clr color.RGBA, i int) {
barOffsetSize := 7
barOffset := -barOffsetSize + (i * barOffsetSize)
barWidth := 7
barX := rciX + buttonWidth/2 - barWidth/2 + barOffset + 4
barY := rciY + (rciSize / 2)
if demand < 0 {
barY += rciButtonHeight / 2
} else {
barY -= rciButtonHeight / 2
}
barHeight := int((float64(rciSize) / 2) * demand)
s.tmpImg.SubImage(image.Rect(barX, barY, barX+barWidth, barY-barHeight)).(*ebiten.Image).Fill(clr)
}
powerColor := colorPowerNormal
if world.World.HavePowerOut || world.World.PowerNeeded > world.World.PowerAvailable {
powerColor = colorPowerOut
}
max := world.World.PowerNeeded
if world.World.PowerAvailable > max {
max = world.World.PowerAvailable
}
pctUsage, pctCapacity := float64(world.World.PowerNeeded)/float64(max), float64(world.World.PowerAvailable)/float64(max)
clamp := func(v float64) float64 {
if math.IsNaN(v) {
return 0
}
if v < -1 {
v = -1
} else if v > 1 {
v = 1
}
return v
}
drawPowerBar(clamp(pctUsage), powerColor, 0)
drawPowerBar(clamp(pctCapacity), colorPowerCapacity, 1)
// Draw button.
const rciButtonPadding = 12
const rciButtonLabelPaddingX = 6
const rciButtonLabelPaddingY = 1
rciButtonY := rciY + (rciSize / 2) - (rciButtonHeight / 2)
rciButtonRect := image.Rect(rciX+rciButtonPadding, rciButtonY, rciX+buttonWidth-rciButtonPadding, rciButtonY+rciButtonHeight)
s.drawButtonBackground(s.tmpImg, rciButtonRect, false) // TODO
// Draw label.
ebitenutil.DebugPrintAt(s.tmpImg, "POWER", rciX+rciButtonPadding+rciButtonLabelPaddingX, rciButtonY+rciButtonLabelPaddingY)
s.drawButtonBorder(s.tmpImg, rciButtonRect, false) // TODO
}
func (s *RenderHudSystem) drawMessages() {
lines := len(world.World.Messages)
if lines == 0 {
return
}
/*var label string
max := maxLen(world.World.Messages)
for i := lines - 1; i >= 0; i-- {
if i != lines-1 {
label += "\n"
}
for j := max - len(world.World.Messages[i]); j > 0; j-- {
label += " "
}
label += world.World.Messages[i]
}*/
label := world.World.Messages[len(world.World.Messages)-1]
max := len(label)
lines = 1
2022-01-23 00:21:02 +00:00
const padding = 10
scale := 2.0
2022-01-23 00:21:02 +00:00
w, h := (max*6+10)*int(scale), 16*(int(scale))*lines+10
x, y := world.World.ScreenW-w, 0
2022-01-14 07:14:22 +00:00
r := image.Rect(x, y, x+w, y+h)
2022-01-15 00:58:44 +00:00
s.hudImg.SubImage(r).(*ebiten.Image).Fill(color.RGBA{0, 0, 0, 120})
2022-01-14 07:14:22 +00:00
s.tmpImg.Clear()
ebitenutil.DebugPrint(s.tmpImg, label)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
2022-01-23 00:21:02 +00:00
op.GeoM.Translate(float64(x)+padding, 3)
2022-01-14 07:14:22 +00:00
s.hudImg.DrawImage(s.tmpImg, op)
}
2022-01-15 00:58:44 +00:00
func (s *RenderHudSystem) drawDate(y int) {
const datePadding = 10
month, year := world.Date()
label := month
scale := 2.0
x, y := datePadding, y
s.tmpImg2.Clear()
ebitenutil.DebugPrint(s.tmpImg2, label)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(float64(x), float64(y))
s.hudImg.DrawImage(s.tmpImg2, op)
label = year
x = world.SidebarWidth - 1 - datePadding - (len(label) * 6 * int(scale))
s.tmpImg2.Clear()
ebitenutil.DebugPrint(s.tmpImg2, label)
op.GeoM.Reset()
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(float64(x), float64(y))
s.hudImg.DrawImage(s.tmpImg2, op)
}
2022-01-19 23:32:02 +00:00
func (s *RenderHudSystem) drawFunds(y int) {
label := world.World.Printer.Sprintf("$%d", world.World.Funds)
scale := 2.0
x, y := world.SidebarWidth/2-(len(label)*12)/2, y
s.tmpImg2.Clear()
ebitenutil.DebugPrint(s.tmpImg2, label)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(float64(x), float64(y))
s.hudImg.DrawImage(s.tmpImg2, op)
}
2022-01-23 00:21:02 +00:00
func (s *RenderHudSystem) drawHelp() {
if world.World.HelpPage < 0 {
return
}
if world.World.HelpUpdated {
s.helpImg.Fill(s.sidebarColor)
label := strings.TrimSpace(world.HelpText[world.World.HelpPage])
s.tmpImg.Clear()
ebitenutil.DebugPrint(s.tmpImg, label)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(2, 2)
op.GeoM.Translate(5, 0)
s.helpImg.DrawImage(s.tmpImg, op)
s.helpImg.SubImage(image.Rect(0, 0, helpW, 1)).(*ebiten.Image).Fill(color.Black)
s.helpImg.SubImage(image.Rect(0, 0, 1, helpH)).(*ebiten.Image).Fill(color.Black)
// Draw prev/next buttons.
buttonSize := 32
buttonPadding := 4
prevRect := image.Rect(buttonPadding+2, helpH-buttonSize-buttonPadding+1, buttonSize+buttonPadding+2, helpH-buttonPadding+1)
closeRect := image.Rect(helpW/2-buttonSize/2, helpH-buttonSize-buttonPadding+1, helpW/2+buttonSize/2, helpH-buttonPadding+1)
nextRect := image.Rect(helpW-buttonPadding, helpH-buttonSize-buttonPadding+1, helpW-buttonSize-buttonPadding, helpH-buttonPadding+1)
drawButton := func(r image.Rectangle, l string) {
s.drawButtonBackground(s.helpImg, r, false)
ebitenutil.DebugPrintAt(s.helpImg, l, r.Min.X+buttonSize/2-4, r.Min.Y+buttonSize/2-10)
s.drawButtonBorder(s.helpImg, r, false)
}
if world.World.HelpPage > 0 {
drawButton(prevRect, "<")
}
drawButton(closeRect, "X")
if world.World.HelpPage < len(world.HelpText)-1 {
drawButton(nextRect, ">")
}
world.World.HelpButtonRects = []image.Rectangle{
prevRect,
closeRect,
nextRect,
}
world.World.HelpUpdated = false
}
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(world.World.ScreenW)-helpW, float64(world.World.ScreenH)-helpH)
s.hudImg.DrawImage(s.helpImg, op)
}