Files
radiantwave-legacy/internal/page/audioDevices.go
T
NeilClack 17c088cc2f Update Database
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."
2026-03-23 15:02:07 -05:00

247 lines
6.5 KiB
Go

package page
import (
"fmt"
"github.com/go-gl/gl/v3.3-core/gl"
"github.com/veandco/go-sdl2/sdl"
"gorm.io/gorm"
"rhemawavetech.com/rhemawave/internal/colors"
"rhemawavetech.com/rhemawave/internal/db"
"rhemawavetech.com/rhemawave/internal/fontManager"
"rhemawavetech.com/rhemawave/internal/mixer"
"rhemawavetech.com/rhemawave/internal/shaderManager"
)
type AudioDevices struct {
Base
font *fontManager.FontEntry
textShader *shaderManager.Shader
solidShader *shaderManager.Shader
title StringItem
prompt StringItem
devices []StringItem
highlightIndex int
selectedIndex int
verticalMargin int32
CfgPtr *db.AppConfig
dbPtr *gorm.DB
}
func (p *AudioDevices) Init(ctx *PageCtx) error {
var err error
p.verticalMargin = int32(20)
// 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 := p.Base.Context.CfgPtr.StandardFontSize
// Calculate scales
titleScale := float32(64) / float32(baseFontSize)
itemScale := float32(32) / float32(baseFontSize)
// Create title
p.title, err = NewStringItem("Select an Audio Output Device", p.font, colors.White)
if err != nil {
return fmt.Errorf("creating title: %w", err)
}
p.title.Scale(titleScale)
// Create prompt
p.prompt, err = NewStringItem("Use Arrow Keys to select, Enter to confirm", p.font, colors.White)
if err != nil {
return fmt.Errorf("creating prompt: %w", err)
}
p.prompt.Scale(itemScale)
// Get list of audio output devices
devices := mixer.ListDevices()
if len(devices) == 0 {
// Create a default entry so the page doesn't break
devices = []string{"System Default"}
}
// Get currently selected device from database
currentDevice := p.Base.Context.CfgPtr.SelectedAudioDevice
// Create UI items for each device
for i, name := range devices { // Iterate over the devices
item, err := NewStringItem(name, p.font, colors.White) // Create a new string string texture
if err != nil {
return fmt.Errorf("failed to create device item name: %w", err)
}
item.Scale(itemScale) // Scale the new string item
p.devices = append(p.devices, item) // Add the new scaled string item to the list of items
// Mark currently selected device
if name == currentDevice {
p.selectedIndex = i
p.highlightIndex = i // Start with cursor on current device
}
}
on := p.Base.Context.CfgPtr.AudioEnabled
audioStatus := "Turn Audio On"
if on {
audioStatus = "Turn Audio Off"
}
audioStatusStringItem, err := NewStringItem(audioStatus, p.font, colors.White)
if err != nil {
return fmt.Errorf("failed to create Turn Audio Off string item: %w", err)
}
// Scale the new option
audioStatusStringItem.Scale(itemScale)
p.devices = append(p.devices, audioStatusStringItem)
p.layout()
return nil
}
func (p *AudioDevices) 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 device items stacked upward from prompt
for i := range p.devices {
p.devices[i].X = p.ScreenCenterX - p.devices[i].W/2
if i == 0 {
p.devices[i].Y = p.prompt.Y - p.verticalMargin*2 - p.devices[i].H
} else {
p.devices[i].Y = p.devices[i-1].Y - p.devices[i].H - 10
}
}
}
func (p *AudioDevices) HandleEvent(event *sdl.Event) error {
e, ok := (*event).(*sdl.KeyboardEvent)
if !ok || 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.devices)-1 {
p.highlightIndex++
}
case sdl.K_RETURN:
selection := p.devices[p.highlightIndex].content
// Handle audio disable
if selection == "Turn Audio Off" || selection == "Turn Audio On" {
newStatus := !p.Base.Context.CfgPtr.AudioEnabled
updatedConfig := p.Base.Context.CfgPtr
updatedConfig.AudioEnabled = newStatus
p.Base.Context.DbPtr.Save(&updatedConfig)
p.Base.Context.RefreshConfig()
return nil
}
// Map "System Default" to empty string for SDL
deviceName := selection
if selection == "System Default" {
deviceName = ""
}
// Validate by attempting the switch
if err := mixer.SwitchDevice(deviceName); err != nil {
return fmt.Errorf("failed to switch to device %q: %w", selection, err)
}
// Save settings
p.Base.Context.CfgPtr.SelectedAudioDevice = deviceName
p.Base.Context.CfgPtr.AudioEnabled = true
p.Base.Context.DbPtr.Save(p.Base.Context.CfgPtr)
p.Base.Context.RefreshConfig()
case sdl.K_BACKSPACE:
p.Base.Context.NavigateTo(SETTINGS)
}
return nil
}
func (p *AudioDevices) 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 devices to display
if len(p.devices) == 0 {
return nil
}
// Render highlight box behind highlighted item
p.RenderSolidColorQuad(
p.solidShader,
p.devices[p.highlightIndex].Position(),
p.devices[p.highlightIndex].RawDimensions(),
colors.White,
)
// Render device items with appropriate colors
for i, item := range p.devices {
if i == p.highlightIndex && i == p.selectedIndex {
// Currently highlighted AND selected device - dark green
item.Color = colors.DarkGreen
} else if i == p.highlightIndex {
// Just highlighted - black (shows on white background)
item.Color = colors.Black
} else if i == p.selectedIndex {
// Just selected - green
item.Color = colors.Green
} else {
// Normal item - white
item.Color = colors.White
}
p.RenderTexture(p.textShader, item.ID, item.Position(), item.RawDimensions(), item.Color)
}
return nil
}
func (p *AudioDevices) Destroy() error {
gl.DeleteTextures(1, &p.title.ID)
gl.DeleteTextures(1, &p.prompt.ID)
for _, item := range p.devices {
gl.DeleteTextures(1, &item.ID)
}
return nil
}