Database is more usable now. All pages have been updated to use the database, and share an application configuration. Page Stack has been removed, no more pages hanging around in memory. Replaced with a 'Router' which uses an Enum to determine which page should be displayed, destroys the old page and initializes the new page all in one go. No more push, pop, rollback, etc. No more holding instances of pages. Logger has been updated with proper timestamps and sorting now works as expected."
240 lines
6.6 KiB
Go
240 lines
6.6 KiB
Go
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
|
|
}
|