Initial release: RadiantWave v1.0
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"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/keybinds"
|
||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||
)
|
||||
|
||||
type downloadState int
|
||||
|
||||
const (
|
||||
downloadStateDownloading downloadState = iota
|
||||
downloadStateFinished
|
||||
)
|
||||
|
||||
// DownloadProgressPage displays a progress bar while an audio file is downloading.
|
||||
// The page is inescapable: no input is accepted while downloading, and only
|
||||
// Enter is accepted once the download finishes.
|
||||
type DownloadProgressPage struct {
|
||||
Base
|
||||
textShader *shaderManager.Shader
|
||||
solidShader *shaderManager.Shader
|
||||
|
||||
// Set by the application before pushing this page.
|
||||
TotalBytes *atomic.Int64
|
||||
BytesWritten *atomic.Int64
|
||||
Done *atomic.Bool
|
||||
HasError *atomic.Bool
|
||||
NextPage Page // where to navigate on success
|
||||
|
||||
state downloadState
|
||||
title StringItem
|
||||
prompt StringItem
|
||||
percentText StringItem
|
||||
lastPercent int
|
||||
|
||||
barX, barY, barW, barH int32
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Init(app ApplicationInterface) error {
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
fm := fontManager.Get()
|
||||
sm := shaderManager.Get()
|
||||
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get font")
|
||||
}
|
||||
|
||||
p.textShader, ok = sm.Get("text")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get text shader")
|
||||
}
|
||||
p.solidShader, ok = sm.Get("solid_color")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get solid_color shader")
|
||||
}
|
||||
|
||||
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: %w", err)
|
||||
}
|
||||
|
||||
titleScale := float32(48) / float32(baseFontSize)
|
||||
p.title, err = NewStringItem("Downloading Audio...", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating title: %w", err)
|
||||
}
|
||||
p.title.Scale(titleScale)
|
||||
|
||||
promptScale := float32(32) / float32(baseFontSize)
|
||||
p.prompt, err = NewStringItem("Press Enter to continue", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating prompt: %w", err)
|
||||
}
|
||||
p.prompt.Scale(promptScale)
|
||||
|
||||
p.percentText, err = NewStringItem("0%", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating percent text: %w", err)
|
||||
}
|
||||
p.percentText.Scale(promptScale)
|
||||
p.lastPercent = 0
|
||||
|
||||
// Progress bar dimensions: 60% of screen width, 30px tall, centered
|
||||
p.barW = int32(float32(p.ScreenWidth) * 0.6)
|
||||
p.barH = 30
|
||||
p.barX = p.ScreenCenterX - p.barW/2
|
||||
p.barY = p.ScreenCenterY - p.barH/2
|
||||
|
||||
// Position title above bar
|
||||
p.title.X = p.ScreenCenterX - p.title.W/2
|
||||
p.title.Y = p.barY + p.barH + 40
|
||||
|
||||
p.state = downloadStateDownloading
|
||||
keybinds.Enabled = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Update(dt float32) error {
|
||||
if p.state == downloadStateFinished {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Done.Load() {
|
||||
p.state = downloadStateFinished
|
||||
|
||||
// Force progress to 100% on success
|
||||
if !p.HasError.Load() && p.TotalBytes.Load() > 0 {
|
||||
p.BytesWritten.Store(p.TotalBytes.Load())
|
||||
p.lastPercent = 100
|
||||
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if ok {
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
percentScale := float32(32) / float32(baseFontSize)
|
||||
newText, err := NewStringItem("100%", font, colors.White)
|
||||
if err == nil {
|
||||
newText.Scale(percentScale)
|
||||
p.percentText = newText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update title to reflect result
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
titleScale := float32(48) / float32(baseFontSize)
|
||||
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
|
||||
var titleText string
|
||||
if p.HasError.Load() {
|
||||
titleText = "Download Failed"
|
||||
} else {
|
||||
titleText = "Download Complete"
|
||||
}
|
||||
newTitle, err := NewStringItem(titleText, font, colors.White)
|
||||
if err == nil {
|
||||
newTitle.Scale(titleScale)
|
||||
p.title = newTitle
|
||||
p.title.X = p.ScreenCenterX - p.title.W/2
|
||||
p.title.Y = p.barY + p.barH + 40
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update percentage texture when it changes
|
||||
percent := 0
|
||||
if p.TotalBytes.Load() > 0 {
|
||||
percent = int(p.BytesWritten.Load() * 100 / p.TotalBytes.Load())
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
}
|
||||
|
||||
if percent != p.lastPercent {
|
||||
p.lastPercent = percent
|
||||
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
percentScale := float32(32) / float32(baseFontSize)
|
||||
|
||||
newText, err := NewStringItem(fmt.Sprintf("%d%%", percent), font, colors.White)
|
||||
if err == nil {
|
||||
newText.Scale(percentScale)
|
||||
p.percentText = newText
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) HandleEvent(event *sdl.Event) error {
|
||||
// Inescapable while downloading
|
||||
if p.state != downloadStateFinished {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch e := (*event).(type) {
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type != sdl.KEYDOWN {
|
||||
return nil
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_RETURN {
|
||||
if p.HasError.Load() || p.NextPage == nil {
|
||||
p.App.SchedulePop()
|
||||
} else {
|
||||
p.App.SwitchPage(p.NextPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Render() error {
|
||||
// Render title
|
||||
p.RenderTexture(p.textShader, p.title.ID, p.title.Position(), p.title.RawDimensions(), p.title.Color)
|
||||
|
||||
// Progress bar background (dark gray)
|
||||
bgColor := sdl.Color{R: 60, G: 60, B: 60, A: 255}
|
||||
barPos := mgl32.Vec2{float32(p.barX), float32(p.barY)}
|
||||
barDim := mgl32.Vec2{float32(p.barW), float32(p.barH)}
|
||||
p.RenderSolidColorQuad(p.solidShader, barPos, barDim, bgColor)
|
||||
|
||||
// Progress bar foreground
|
||||
progress := float32(0)
|
||||
if p.TotalBytes.Load() > 0 {
|
||||
progress = float32(p.BytesWritten.Load()) / float32(p.TotalBytes.Load())
|
||||
if progress > 1.0 {
|
||||
progress = 1.0
|
||||
}
|
||||
}
|
||||
if progress > 0 {
|
||||
fgColor := sdl.Color{R: 255, G: 255, B: 255, A: 255}
|
||||
fgDim := mgl32.Vec2{float32(p.barW) * progress, float32(p.barH)}
|
||||
p.RenderSolidColorQuad(p.solidShader, barPos, fgDim, fgColor)
|
||||
}
|
||||
|
||||
// Render percentage text below bar
|
||||
p.percentText.X = p.ScreenCenterX - p.percentText.W/2
|
||||
p.percentText.Y = p.barY - p.percentText.H - 20
|
||||
p.RenderTexture(p.textShader, p.percentText.ID, p.percentText.Position(), p.percentText.RawDimensions(), p.percentText.Color)
|
||||
|
||||
// Show prompt when finished
|
||||
if p.state == downloadStateFinished {
|
||||
p.prompt.X = p.ScreenCenterX - p.prompt.W/2
|
||||
p.prompt.Y = p.barY - p.percentText.H - 20 - p.prompt.H - 20
|
||||
p.RenderTexture(p.textShader, p.prompt.ID, p.prompt.Position(), p.prompt.RawDimensions(), p.prompt.Color)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Destroy() error {
|
||||
keybinds.Enabled = true
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
gl.DeleteTextures(1, &p.prompt.ID)
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user