277 lines
7.2 KiB
Go
277 lines
7.2 KiB
Go
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
|
|
}
|