package menu import ( "image/color" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" "github.com/hajimehoshi/ebiten/v2/vector" "golang.org/x/image/font/basicfont" "github.com/atridad/LilGuy/internal/screens" ) var ( basicFace = text.NewGoXFace(basicfont.Face7x13) basicFaceAscent = basicFace.Metrics().HAscent overlayImage *ebiten.Image overlaySize struct { width int height int } ) // Menu options func getOverlayImage(width, height int) *ebiten.Image { if width <= 0 || height <= 0 { return nil } if overlayImage == nil || overlaySize.width != width || overlaySize.height != height { overlayImage = ebiten.NewImage(width, height) overlayImage.Fill(color.RGBA{R: 0, G: 0, B: 0, A: 180}) overlaySize.width = width overlaySize.height = height } return overlayImage } type MenuOption int const ( OptionResume MenuOption = iota OptionSave OptionSettings OptionMainMenu OptionQuit optionCount ) // Screen modes type menuScreen int const ( screenMain menuScreen = iota screenSettings ) type PauseMenu struct { selectedIndex int currentScreen menuScreen settingsScreen *screens.SettingsScreen } type FPSCapSetting interface { String() string Cycle() } func NewPauseMenu() *PauseMenu { return &PauseMenu{ selectedIndex: 0, currentScreen: screenMain, settingsScreen: screens.NewSettingsScreen(), } } func (m *PauseMenu) SetFPSMonitor(enabled *bool) { m.settingsScreen.SetFPSMonitor(enabled) } func (m *PauseMenu) SetFPSCap(cap FPSCapSetting) { m.settingsScreen.SetFPSCap(cap) } func (m *PauseMenu) SetPortalVisibility(enabled *bool) { m.settingsScreen.SetPortalVisibility(enabled) } func (m *PauseMenu) SetRaycastSettings(enabled *bool, debugMode *bool) { m.settingsScreen.SetRaycastSettings(enabled, debugMode) } // Update logic func (m *PauseMenu) Update() *MenuOption { if m.currentScreen == screenSettings { return m.updateSettings() } return m.updateMain() } func (m *PauseMenu) updateMain() *MenuOption { if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) { m.selectedIndex-- if m.selectedIndex < 0 { m.selectedIndex = int(optionCount) - 1 } } if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) { m.selectedIndex++ if m.selectedIndex >= int(optionCount) { m.selectedIndex = 0 } } if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) { selected := MenuOption(m.selectedIndex) if selected == OptionSettings { m.currentScreen = screenSettings m.selectedIndex = 0 return nil } return &selected } return nil } func (m *PauseMenu) updateSettings() *MenuOption { if m.settingsScreen.Update() { m.currentScreen = screenMain m.selectedIndex = 0 } return nil } // Rendering func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) { if overlay := getOverlayImage(screenWidth, screenHeight); overlay != nil { screen.DrawImage(overlay, nil) } menuWidth := 400 menuHeight := 380 menuX := (screenWidth - menuWidth) / 2 menuY := (screenHeight - menuHeight) / 2 vector.FillRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), color.RGBA{R: 40, G: 40, B: 50, A: 255}, false, ) vector.StrokeRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), 2, color.RGBA{R: 100, G: 100, B: 120, A: 255}, false, ) if m.currentScreen == screenSettings { m.drawSettings(screen, menuX, menuY, menuWidth, menuHeight) } else { m.drawMain(screen, menuX, menuY, menuWidth, menuHeight) } } func (m *PauseMenu) drawMain(screen *ebiten.Image, menuX, menuY, menuWidth, menuHeight int) { titleText := "PAUSED" titleX := menuX + (menuWidth / 2) - (len(titleText) * 7 / 2) titleY := menuY + 50 m.drawText(screen, titleText, color.White, titleX, titleY) options := []string{"Resume", "Save", "Settings", "Main Menu", "Quit"} startY := menuY + 90 for i, option := range options { optionY := startY + (i * 45) optionX := menuX + (menuWidth / 2) - (len(option) * 7 / 2) if i == m.selectedIndex { indicatorX := optionX - 20 m.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, optionY) m.drawText(screen, option, color.RGBA{R: 255, G: 255, B: 100, A: 255}, optionX, optionY) } else { m.drawText(screen, option, color.RGBA{R: 180, G: 180, B: 180, A: 255}, optionX, optionY) } } // Instructions hintText := "Use Arrow Keys/WASD to navigate, Enter/Space to select" hintX := menuX + (menuWidth / 2) - (len(hintText) * 7 / 2) hintY := menuY + menuHeight - 30 m.drawText(screen, hintText, color.RGBA{R: 150, G: 150, B: 150, A: 255}, hintX, hintY) } func (m *PauseMenu) drawSettings(screen *ebiten.Image, menuX, menuY, menuWidth, menuHeight int) { vector.FillRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), color.RGBA{R: 40, G: 40, B: 50, A: 255}, false, ) vector.StrokeRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), 2, color.RGBA{R: 100, G: 100, B: 120, A: 255}, false, ) screenWidth := menuWidth screenHeight := menuHeight subScreen := ebiten.NewImage(screenWidth, screenHeight) m.settingsScreen.Draw(subScreen, screenWidth, screenHeight, "SETTINGS") op := &ebiten.DrawImageOptions{} op.GeoM.Translate(float64(menuX), float64(menuY)) screen.DrawImage(subScreen, op) } func (m *PauseMenu) drawText(screen *ebiten.Image, txt string, clr color.Color, x, y int) { op := &text.DrawOptions{} op.GeoM.Translate(float64(x), float64(y)-basicFaceAscent) op.ColorScale.ScaleWithColor(clr) text.Draw(screen, txt, basicFace, op) } func (m *PauseMenu) Reset() { m.selectedIndex = 0 m.currentScreen = screenMain m.settingsScreen.Reset() }