package page import ( "fmt" "github.com/go-gl/gl/v3.3-core/gl" "github.com/veandco/go-sdl2/sdl" "rhemawavetech.com/rhemawave/internal/colors" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/logger" "rhemawavetech.com/rhemawave/internal/shaderManager" ) type ProclamationOptions struct { Base font *fontManager.FontEntry textShader *shaderManager.Shader solidShader *shaderManager.Shader title StringItem prompt StringItem proclamations []StringItem proclamationIDs []string activeMap map[int]bool // Maps display index to selected state highlightIndex int verticalMargin int32 } func (p *ProclamationOptions) Init(ctx *PageCtx) error { var err error p.verticalMargin = int32(20) p.activeMap = make(map[int]bool) p.proclamationIDs = make([]string, 0) // Get font fm := fontManager.Get() font, ok := fm.GetFont("Roboto-Regular") if !ok { return fmt.Errorf("failed to get font: %w", sdl.GetError()) } p.font = font // Get shaders sm := shaderManager.Get() p.textShader, ok = sm.Get("text") if !ok { return fmt.Errorf("failed to get text shader: %w", sdl.GetError()) } p.solidShader, ok = sm.Get("solid_color") if !ok { return fmt.Errorf("failed to get solid_color shader: %w", sdl.GetError()) } // Initialize base if err := p.Base.Init(ctx); err != nil { return fmt.Errorf("base init failed: %w", err) } // Get font size from database baseFontSize := ctx.CfgPtr.StandardFontSize // Calculate scales titleScale := float32(64) / float32(baseFontSize) itemScale := float32(32) / float32(baseFontSize) // Create title p.title, err = NewStringItem("Select Proclamations", p.font, colors.White) if err != nil { return fmt.Errorf("creating title: %w", err) } p.title.Scale(titleScale) // Create prompt p.prompt, err = NewStringItem("Arrow Keys to navigate | Space to toggle | F2 to run | Enter to return", p.font, colors.White) if err != nil { return fmt.Errorf("creating prompt: %w", err) } p.prompt.Scale(itemScale) // Load proclamations from database var proclamations []db.Proclamation if result := p.Context.DbPtr.Find(&proclamations); result.Error != nil { logger.Error("unable to query database for proclamations: %v", result.Error) return fmt.Errorf("%s", result.Error) } // Create UI items for each available proclamation for _, proclamation := range proclamations { if !proclamation.Available { continue } label, err := NewStringItem(proclamation.Name, p.font, colors.White) if err != nil { return fmt.Errorf("creating proclamation label: %w", err) } label.Scale(itemScale) p.proclamations = append(p.proclamations, label) p.proclamationIDs = append(p.proclamationIDs, proclamation.ID) // Set initial selection state if proclamation.Selected { p.activeMap[len(p.proclamations)-1] = true } } p.layout() return nil } func (p *ProclamationOptions) layout() { // Position title at bottom p.title.X = p.ScreenCenterX - p.title.W/2 p.title.Y = p.ScreenHeight - p.title.H - p.verticalMargin*2 // Position prompt above title p.prompt.X = p.ScreenCenterX - p.prompt.W/2 p.prompt.Y = p.title.Y - p.verticalMargin*2 - p.prompt.H // Position proclamation items stacked upward from prompt for i := range p.proclamations { p.proclamations[i].X = p.ScreenCenterX - p.proclamations[i].W/2 if i == 0 { p.proclamations[i].Y = p.prompt.Y - p.verticalMargin*2 - p.proclamations[i].H } else { p.proclamations[i].Y = p.proclamations[i-1].Y - p.proclamations[i].H - 10 } } } func (p *ProclamationOptions) HandleEvent(event *sdl.Event) error { switch e := (*event).(type) { case *sdl.KeyboardEvent: if e.Type != sdl.KEYDOWN { return nil } switch e.Keysym.Sym { case sdl.K_UP: if p.highlightIndex > 0 { p.highlightIndex-- } case sdl.K_DOWN: if p.highlightIndex < len(p.proclamations)-1 { p.highlightIndex++ } case sdl.K_BACKSPACE: p.Context.NavigateTo(SETTINGS) case sdl.K_SPACE: // Toggle selection for highlighted item if p.activeMap[p.highlightIndex] { delete(p.activeMap, p.highlightIndex) } else { p.activeMap[p.highlightIndex] = true } // Immediately save to database if err := p.toggleProclamation(p.highlightIndex); err != nil { return fmt.Errorf("failed to toggle proclamation: %w", err) } case sdl.K_RETURN: // Just return to settings - no saving needed p.Context.NavigateTo(SETTINGS) } } return nil } // toggleProclamation immediately updates the database for the proclamation at the given display index func (p *ProclamationOptions) toggleProclamation(displayIndex int) error { if displayIndex < 0 || displayIndex >= len(p.proclamationIDs) { return fmt.Errorf("invalid display index %d", displayIndex) } dbID := p.proclamationIDs[displayIndex] isSelected := p.activeMap[displayIndex] // Get the proclamation from database var proclamation db.Proclamation proclamation.ID = dbID if result := p.Context.DbPtr.Find(&proclamation); result.Error != nil { logger.Error("unable to query proclamation %s from database: %v", proclamation.ID, result.Error) } // Update the Selected field proclamation.Selected = isSelected if result := p.Context.DbPtr.Save(&proclamation); result.Error != nil { logger.Error("failed to update proclamation %s: %v", proclamation.ID, result.Error) return fmt.Errorf("%s", result.Error) } return nil } func (p *ProclamationOptions) Render() error { // Always render title and prompt p.RenderTexture(p.textShader, p.title.ID, p.title.Position(), p.title.RawDimensions(), p.title.Color) p.RenderTexture(p.textShader, p.prompt.ID, p.prompt.Position(), p.prompt.RawDimensions(), p.prompt.Color) // No proclamations to display if len(p.proclamations) == 0 { return nil } // Render highlight box behind selected item p.RenderSolidColorQuad( p.solidShader, p.proclamations[p.highlightIndex].Position(), p.proclamations[p.highlightIndex].RawDimensions(), colors.White, ) // Render proclamation items with appropriate colors for i, item := range p.proclamations { if p.activeMap[i] { // Selected items are green item.Color = colors.Green } else if i == p.highlightIndex { // Highlighted item is black (shows on white background) item.Color = colors.Black } else { // Normal items are white item.Color = colors.White } p.RenderTexture(p.textShader, item.ID, item.Position(), item.RawDimensions(), item.Color) } return nil } func (p *ProclamationOptions) Destroy() error { gl.DeleteTextures(1, &p.title.ID) gl.DeleteTextures(1, &p.prompt.ID) for _, item := range p.proclamations { gl.DeleteTextures(1, &item.ID) } return nil }