274 lines
7.3 KiB
Go
274 lines
7.3 KiB
Go
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/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
|
|
}
|
|
|
|
func (p *AudioDevices) Init(app ApplicationInterface) 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(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 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, err := db.GetConfigValue("audio_device_name")
|
|
if err != nil {
|
|
return fmt.Errorf("retrieving audio_device_name from db: %w", err)
|
|
}
|
|
|
|
// 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, err := db.GetConfigValue("audio_enabled")
|
|
if err != nil {
|
|
return fmt.Errorf("unable to query database for audio_enabled: %w", err)
|
|
}
|
|
|
|
audioStatus := "Turn Audio On"
|
|
if on == "true" {
|
|
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" {
|
|
on, err := db.GetConfigValue("audio_enabled")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch audio_enabled from database: %w", err)
|
|
}
|
|
|
|
currentStatus, err := strconv.ParseBool(on)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse audio_enabled value: %w", err)
|
|
}
|
|
|
|
newStatus := strconv.FormatBool(!currentStatus)
|
|
if err := db.SetConfigValue("audio_enabled", newStatus); err != nil {
|
|
return fmt.Errorf("failed to update audio_enabled: %w", err)
|
|
}
|
|
|
|
p.App.UnwindToPage(&Settings{})
|
|
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
|
|
if err := db.SetConfigValue("audio_device_name", deviceName); err != nil {
|
|
return fmt.Errorf("failed to save audio device: %w", err)
|
|
}
|
|
if err := db.SetConfigValue("audio_enabled", "true"); err != nil {
|
|
return fmt.Errorf("failed to enable audio: %w", err)
|
|
}
|
|
|
|
p.App.UnwindToPage(&Settings{})
|
|
|
|
case sdl.K_BACKSPACE:
|
|
p.App.UnwindToPage(&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
|
|
}
|