Initial release: RadiantWave v1.0

This commit is contained in:
NeilClack
2026-03-12 08:05:10 -05:00
commit 403ec4db89
555 changed files with 437856 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
package page
import (
"fmt"
"strconv"
"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/shaderManager"
)
type ProclamationOptions struct {
Base
font *fontManager.FontEntry
textShader *shaderManager.Shader
solidShader *shaderManager.Shader
title StringItem
prompt StringItem
proclamations []StringItem
proclamationIDs []uint // Database IDs corresponding to each proclamation
activeMap map[int]bool // Maps display index to selected state
highlightIndex int
verticalMargin int32
}
func (p *ProclamationOptions) Init(app ApplicationInterface) error {
var err error
p.verticalMargin = int32(20)
p.activeMap = make(map[int]bool)
p.proclamationIDs = make([]uint, 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(app); err != nil {
return fmt.Errorf("base init failed: %w", err)
}
// Get font size from database
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
if err != nil {
return fmt.Errorf("retrieving init_font_size from db: %w", err)
}
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
if err != nil {
return fmt.Errorf("parsing init_font_size %q: %w", baseFontSizeStr, err)
}
// 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
proclamations, err := db.GetProclamations()
if err != nil {
return fmt.Errorf("retrieving proclamations from db: %w", err)
}
// Create UI items for each available proclamation
for _, proclamation := range proclamations {
if !proclamation.Available {
continue
}
label, err := NewStringItem(proclamation.Title, 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.App.UnwindToPage(&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.App.UnwindToPage(&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.Proclamations
if err := db.DB.First(&proclamation, dbID).Error; err != nil {
return fmt.Errorf("retrieving proclamation %d: %w", dbID, err)
}
// Update the Selected field
proclamation.Selected = isSelected
if err := db.DB.Save(&proclamation).Error; err != nil {
return fmt.Errorf("updating proclamation %d: %w", dbID, err)
}
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
}