package screens import ( "image/color" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" ) // Settings interface type FPSCapSetting interface { String() string Cycle() } type settingsScreen int const ( settingsMain settingsScreen = iota settingsDebugOptions ) type SettingsScreen struct { selectedIndex int currentScreen settingsScreen fpsMonitorValue *bool fpsCapValue FPSCapSetting portalVisibilityValue *bool raycastEnabledValue *bool raycastDebugValue *bool } func NewSettingsScreen() *SettingsScreen { return &SettingsScreen{ selectedIndex: 0, currentScreen: settingsMain, } } func (s *SettingsScreen) SetFPSMonitor(enabled *bool) { s.fpsMonitorValue = enabled } func (s *SettingsScreen) SetFPSCap(cap FPSCapSetting) { s.fpsCapValue = cap } func (s *SettingsScreen) SetPortalVisibility(enabled *bool) { s.portalVisibilityValue = enabled } func (s *SettingsScreen) SetRaycastSettings(enabled *bool, debugMode *bool) { s.raycastEnabledValue = enabled s.raycastDebugValue = debugMode } func (s *SettingsScreen) Update() bool { if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { if s.currentScreen == settingsDebugOptions { s.currentScreen = settingsMain s.selectedIndex = 0 return false } return true } if s.currentScreen == settingsDebugOptions { return s.updateDebugOptions() } return s.updateMain() } func (s *SettingsScreen) updateMain() bool { settingsCount := 2 if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) { s.selectedIndex-- if s.selectedIndex < 0 { s.selectedIndex = 0 } } if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) { s.selectedIndex++ if s.selectedIndex >= settingsCount { s.selectedIndex = settingsCount - 1 } } if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) { if s.selectedIndex == 0 && s.fpsCapValue != nil { s.fpsCapValue.Cycle() } else if s.selectedIndex == 1 { s.currentScreen = settingsDebugOptions s.selectedIndex = 0 } } return false } func (s *SettingsScreen) updateDebugOptions() bool { debugOptionsCount := 5 // FPS Monitor, Portal Visibility, Raycast Enabled, Raycast Debug, Back if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) { s.selectedIndex-- if s.selectedIndex < 0 { s.selectedIndex = 0 } } if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) { s.selectedIndex++ if s.selectedIndex >= debugOptionsCount { s.selectedIndex = debugOptionsCount - 1 } } if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) { if s.selectedIndex == 0 && s.fpsMonitorValue != nil { *s.fpsMonitorValue = !*s.fpsMonitorValue } else if s.selectedIndex == 1 && s.portalVisibilityValue != nil { *s.portalVisibilityValue = !*s.portalVisibilityValue } else if s.selectedIndex == 2 && s.raycastEnabledValue != nil { *s.raycastEnabledValue = !*s.raycastEnabledValue } else if s.selectedIndex == 3 && s.raycastDebugValue != nil { *s.raycastDebugValue = !*s.raycastDebugValue } else if s.selectedIndex == 4 { s.currentScreen = settingsMain s.selectedIndex = 0 } } return false } // Rendering func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight int, title string) { screen.Fill(color.RGBA{R: 20, G: 20, B: 30, A: 255}) titleX := (screenWidth / 2) - (len(title) * 7 / 2) titleY := screenHeight/3 - 50 s.drawText(screen, title, color.White, titleX, titleY) if s.currentScreen == settingsDebugOptions { s.drawDebugOptions(screen, screenWidth, screenHeight) } else { s.drawMain(screen, screenWidth, screenHeight) } } func (s *SettingsScreen) drawMain(screen *ebiten.Image, screenWidth, screenHeight int) { startY := screenHeight/2 - 20 leftMargin := screenWidth/2 - 120 // FPS cap setting fpsCapText := "FPS Cap: " if s.fpsCapValue != nil { fpsCapText += s.fpsCapValue.String() } else { fpsCapText += "60 FPS" } if s.selectedIndex == 0 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, startY) s.drawText(screen, fpsCapText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, startY) } else { s.drawText(screen, fpsCapText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, startY) } // debug options submenu debugOptionsText := "Debug Options >" debugY := startY + 40 if s.selectedIndex == 1 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, debugY) s.drawText(screen, debugOptionsText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, debugY) } else { s.drawText(screen, debugOptionsText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, debugY) } // Instructions hintText := "Enter/Space to select, ESC to go back" hintX := (screenWidth / 2) - (len(hintText) * 7 / 2) hintY := screenHeight - 50 s.drawText(screen, hintText, color.RGBA{R: 120, G: 120, B: 150, A: 255}, hintX, hintY) } func (s *SettingsScreen) drawDebugOptions(screen *ebiten.Image, screenWidth, screenHeight int) { startY := screenHeight/2 - 20 leftMargin := screenWidth/2 - 120 // FPS monitor toggle fpsMonitorText := "FPS Monitor: " if s.fpsMonitorValue != nil && *s.fpsMonitorValue { fpsMonitorText += "ON" } else { fpsMonitorText += "OFF" } if s.selectedIndex == 0 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, startY) s.drawText(screen, fpsMonitorText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, startY) } else { s.drawText(screen, fpsMonitorText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, startY) } // Portal visibility toggle portalVisText := "Portal Visibility: " if s.portalVisibilityValue != nil && *s.portalVisibilityValue { portalVisText += "ON" } else { portalVisText += "OFF" } portalY := startY + 40 if s.selectedIndex == 1 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, portalY) s.drawText(screen, portalVisText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, portalY) } else { s.drawText(screen, portalVisText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, portalY) } // Raycast enabled toggle raycastText := "Lighting: " if s.raycastEnabledValue != nil && *s.raycastEnabledValue { raycastText += "ON" } else { raycastText += "OFF" } raycastY := startY + 80 if s.selectedIndex == 2 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, raycastY) s.drawText(screen, raycastText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, raycastY) } else { s.drawText(screen, raycastText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, raycastY) } // Raycast debug toggle raycastDebugText := "Debug Rays: " if s.raycastDebugValue != nil && *s.raycastDebugValue { raycastDebugText += "ON" } else { raycastDebugText += "OFF" } raycastDebugY := startY + 120 if s.selectedIndex == 3 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, raycastDebugY) s.drawText(screen, raycastDebugText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, raycastDebugY) } else { s.drawText(screen, raycastDebugText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, raycastDebugY) } // back option backText := "< Back" backY := startY + 160 if s.selectedIndex == 4 { indicatorX := leftMargin - 20 s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, backY) s.drawText(screen, backText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, backY) } else { s.drawText(screen, backText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, backY) } // Instructions hintText := "Enter/Space to select, ESC to go back" hintX := (screenWidth / 2) - (len(hintText) * 7 / 2) hintY := screenHeight - 50 s.drawText(screen, hintText, color.RGBA{R: 120, G: 120, B: 150, A: 255}, hintX, hintY) } func (s *SettingsScreen) 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 (s *SettingsScreen) Reset() { s.selectedIndex = 0 s.currentScreen = settingsMain }