diff --git a/internal/application/application.go b/internal/application/application.go index 273ae40..52e0475 100644 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -12,6 +12,7 @@ import ( "github.com/veandco/go-sdl2/ttf" "gorm.io/gorm" "rhemawavetech.com/rhemawave/internal/db" + "rhemawavetech.com/rhemawave/internal/download" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/graphics" "rhemawavetech.com/rhemawave/internal/keybinds" @@ -153,18 +154,39 @@ func Run() { app.initializeManagers() app.initializeAudio() + downloadProgressChan := make(chan download.DownloadStatus) + app.PageRouter = page.NewRouter(page.PageCtx{ - CfgPtr: app.Config.Load(), - DbPtr: app.DB, - W: w, - H: h, - RefreshConfig: app.refreshCache, + CfgPtr: app.Config.Load(), + DbPtr: app.DB, + W: w, + H: h, + MessageChannel: downloadProgressChan, + RefreshConfig: app.refreshCache, }) // Sync the proclamations from the API to the Database sync.SyncProclamations(DB) sync.SyncCategories(DB) sync.SyncFrequencies(DB) + audioFileNames := []string{ + "OriginalAudio.wav", + "174Hz.wav", + "396Hz.wav", + "417Hz.wav", + "432Hz.wav", + "639Hz.wav", + "741Hz.wav", + "852Hz.wav", + "963Hz.wav", + "1111Hz.wav", + "14Hz_Binaural.wav", + "40Hz_Binaural.wav", + "AlphaWaves.wav", + "PinealGland.wav", + } + + go download.DownloadAudioFiles(app.PageRouter.Context.MessageChannel, audioFileNames, filepath.Join(app.PageRouter.Context.CfgPtr.AssetsDir, "audio")) app.runEventLoop() } diff --git a/internal/download/Download.go b/internal/download/Download.go index 3d4da08..6a50944 100644 --- a/internal/download/Download.go +++ b/internal/download/Download.go @@ -162,7 +162,7 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, progressChan <- DownloadStatus{ Max: totalSize, Cur: totalDownloaded, - Msg: fmt.Sprintf("Finished downloading %s", filename), + Msg: "Finished Downloading", } } diff --git a/internal/mixer/mixer.go b/internal/mixer/mixer.go index 995dc0c..63932d2 100644 --- a/internal/mixer/mixer.go +++ b/internal/mixer/mixer.go @@ -70,7 +70,7 @@ func Init(cfg *db.AppConfig, onUpdate func()) error { return err } - SetAllSinkVolumes(1.0) + SetAllSinkVolumes(0.3) inited = true return nil diff --git a/internal/page/home.go b/internal/page/home.go index 4f03722..8b128bc 100644 --- a/internal/page/home.go +++ b/internal/page/home.go @@ -2,8 +2,6 @@ package page import ( "fmt" - "path/filepath" - "time" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" @@ -30,8 +28,8 @@ type Home struct { emailDisplay StringItem licenseKeyDisplay StringItem hasLicenseInfo bool - lastDisplayUpdate time.Time - baseFontSize int // cached from db at init + baseFontSize int + isDownloadComplete bool } // Init performs basic page initialization and creation of static assets. @@ -82,6 +80,12 @@ func (p *Home) Init(ctx *PageCtx) error { // Create download channel and message p.downloadProgressChan = make(chan download.DownloadStatus) + // Initialize message + p.message, err = NewStringItem("Status: Initializing...", font, colors.White) + if err != nil { + return fmt.Errorf("unable to create initial message texture: %w", err) + } + // Create license info textures if data exists if p.Context.CfgPtr.EmailAddress != "" || p.Context.CfgPtr.LicenseKey != "" { p.hasLicenseInfo = true @@ -113,44 +117,33 @@ func (p *Home) Init(ctx *PageCtx) error { // Get base font size from database baseFontSize := p.Context.CfgPtr.StandardFontSize - p.baseFontSize = baseFontSize // Scale the note text fontSize := int32(32) scale := float32(fontSize) / float32(baseFontSize) - p.note.Scale(scale * 0.75) + p.note.W = int32(float32(p.note.W) * scale * 0.75) + p.note.H = int32(float32(p.note.H) * scale * 0.75) // Scale license info texts if p.hasLicenseInfo { licenseInfoScale := float32(28) / float32(baseFontSize) - p.licenseInfoHeading.Scale(licenseInfoScale) + p.licenseInfoHeading.W = int32(float32(p.licenseInfoHeading.W) * licenseInfoScale) + p.licenseInfoHeading.H = int32(float32(p.licenseInfoHeading.H) * licenseInfoScale) if p.emailDisplay.ID != 0 { - p.emailDisplay.Scale(licenseInfoScale * 0.85) + p.emailDisplay.W = int32(float32(p.emailDisplay.W) * licenseInfoScale * 0.85) + p.emailDisplay.H = int32(float32(p.emailDisplay.H) * licenseInfoScale * 0.85) } if p.licenseKeyDisplay.ID != 0 { - p.licenseKeyDisplay.Scale(licenseInfoScale * 0.85) + p.licenseKeyDisplay.W = int32(float32(p.licenseKeyDisplay.W) * licenseInfoScale * 0.85) + p.licenseKeyDisplay.H = int32(float32(p.licenseKeyDisplay.H) * licenseInfoScale * 0.85) } } - audioFileNames := []string{ - "OriginalAudio.wav", - "174Hz.wav", - "396Hz.wav", - "417Hz.wav", - "432Hz.wav", - "639Hz.wav", - "741Hz.wav", - "852Hz.wav", - "963Hz.wav", - "1111Hz.wav", - "14Hz_Binaural.wav", - "40Hz_Binaural.wav", - "AlphaWaves.wav", - "PinealGland.wav", - } - - go download.DownloadAudioFiles(p.downloadProgressChan, audioFileNames, filepath.Join(ctx.CfgPtr.AssetsDir, "audio")) + // Scale message text + messageScale := float32(28) / float32(baseFontSize) + p.message.W = int32(float32(p.message.W) * messageScale) + p.message.H = int32(float32(p.message.H) * messageScale) return nil } @@ -162,20 +155,44 @@ func (p *Home) HandleEvent(event *sdl.Event) error { // Update updates animations and changing textures and other changes using `dt` which represents a time delta in float32. func (p *Home) Update(dt float32) error { - var err error - select { - case progressUpdate := <-p.downloadProgressChan: - if progressUpdate.Err != nil { - p.message, err = NewStringItem(progressUpdate.Msg, p.font, colors.White) + case progressUpdate, ok := <-p.Context.MessageChannel: + if !ok { + // Channel is closed, set message to READY + newMsg, err := NewStringItem("Status: READY", p.font, colors.Green) if err != nil { return err } + messageScale := float32(28) / float32(p.baseFontSize) + newMsg.W = int32(float32(newMsg.W) * messageScale) + newMsg.H = int32(float32(newMsg.H) * messageScale) + p.message = newMsg + p.isDownloadComplete = true + } else if progressUpdate.Err != nil { + // Handle error + newMsg, err := NewStringItem(fmt.Sprintf("Status: %s", progressUpdate.Err.Error), p.font, colors.Red) + if err != nil { + return err + } + messageScale := float32(28) / float32(p.baseFontSize) + newMsg.W = int32(float32(newMsg.W) * messageScale) + newMsg.H = int32(float32(newMsg.H) * messageScale) + p.message = newMsg } else { - p.message, err = NewStringItem(progressUpdate.Err.Error(), p.font, colors.Red) + // Update progress message + percentComplete := float64(progressUpdate.Cur) / float64(progressUpdate.Max) * 100 + msg := fmt.Sprintf("Status: Downloading assets %.1f%% Complete", percentComplete) + newMsg, err := NewStringItem(msg, p.font, colors.White) + if err != nil { + return err + } + messageScale := float32(28) / float32(p.baseFontSize) + newMsg.W = int32(float32(newMsg.W) * messageScale) + newMsg.H = int32(float32(newMsg.H) * messageScale) + p.message = newMsg } default: - return nil + // No update, do nothing } return nil } @@ -184,6 +201,7 @@ func (p *Home) Render() error { baseFontSize := p.baseFontSize sm := shaderManager.Get() verticalMargin := int32(20) + licenseInfoMargin := int32(10) // 1) Calculate scales titleFontSize := int32(64) @@ -205,9 +223,26 @@ func (p *Home) Render() error { promptH := float32(p.prompt.H) * bodyScale promptDim := mgl32.Vec2{promptW, promptH} - // p.note already scaled in Init via p.note.Scale(scale*0.75) - noteDim := p.note.RawDimensions() - noteW, noteH := noteDim[0], noteDim[1] + // Note dimensions (already scaled in Init) + noteW := float32(p.note.W) + noteH := float32(p.note.H) + + // Message dimensions (already scaled in Init/Update) + messageW := float32(p.message.W) + messageH := float32(p.message.H) + + // License info dimensions (already scaled in Init) + licenseHeadingW := float32(p.licenseInfoHeading.W) + licenseHeadingH := float32(p.licenseInfoHeading.H) + var emailDisplayW, emailDisplayH, licenseKeyDisplayW, licenseKeyDisplayH float32 + if p.emailDisplay.ID != 0 { + emailDisplayW = float32(p.emailDisplay.W) + emailDisplayH = float32(p.emailDisplay.H) + } + if p.licenseKeyDisplay.ID != 0 { + licenseKeyDisplayW = float32(p.licenseKeyDisplay.W) + licenseKeyDisplayH = float32(p.licenseKeyDisplay.H) + } // 3) Calculate total height of the vertical stack totalH := int32(titleH) + verticalMargin + @@ -218,7 +253,7 @@ func (p *Home) Render() error { // 4) Y-up layout: start from the TOP of the centered stack and step DOWN top := p.Base.ScreenCenterY + totalH/2 - titleY := top - int32(titleH) // Title sits at the top + titleY := top - int32(titleH) versionY := titleY - verticalMargin - int32(versionH) p.prompt.Y = versionY - verticalMargin - int32(promptH) p.note.Y = p.prompt.Y - verticalMargin - int32(noteH) @@ -234,23 +269,8 @@ func (p *Home) Render() error { // 6) Position license info at bottom center if p.hasLicenseInfo { - licenseInfoMargin := int32(10) bottomMargin := int32(20) - // License info items are already scaled in Init - licenseHeadingDim := p.licenseInfoHeading.RawDimensions() - licenseHeadingW, licenseHeadingH := licenseHeadingDim[0], licenseHeadingDim[1] - - var emailDisplayH, licenseKeyDisplayH float32 - if p.emailDisplay.ID != 0 { - emailDisplayDim := p.emailDisplay.RawDimensions() - emailDisplayH = emailDisplayDim[1] - } - if p.licenseKeyDisplay.ID != 0 { - licenseKeyDisplayDim := p.licenseKeyDisplay.RawDimensions() - licenseKeyDisplayH = licenseKeyDisplayDim[1] - } - // Calculate total height of license info block licenseInfoTotalH := int32(licenseHeadingH) if p.emailDisplay.ID != 0 { @@ -265,8 +285,6 @@ func (p *Home) Render() error { // License key (bottom-most if it exists) if p.licenseKeyDisplay.ID != 0 { - licenseKeyDisplayDim := p.licenseKeyDisplay.RawDimensions() - licenseKeyDisplayW := licenseKeyDisplayDim[0] p.licenseKeyDisplay.X = p.Base.ScreenCenterX - int32(licenseKeyDisplayW/2) p.licenseKeyDisplay.Y = currentY currentY += int32(licenseKeyDisplayH) + licenseInfoMargin @@ -274,8 +292,6 @@ func (p *Home) Render() error { // Email if p.emailDisplay.ID != 0 { - emailDisplayDim := p.emailDisplay.RawDimensions() - emailDisplayW := emailDisplayDim[0] p.emailDisplay.X = p.Base.ScreenCenterX - int32(emailDisplayW/2) p.emailDisplay.Y = currentY currentY += int32(emailDisplayH) + licenseInfoMargin @@ -286,6 +302,26 @@ func (p *Home) Render() error { p.licenseInfoHeading.Y = currentY } + // 7) Position message between license info and note/prompt + if p.hasLicenseInfo { + // Calculate the top of the license info block + licenseInfoTopY := p.licenseInfoHeading.Y + int32(licenseHeadingH) + if p.emailDisplay.ID != 0 { + licenseInfoTopY += int32(emailDisplayH) + licenseInfoMargin + } + if p.licenseKeyDisplay.ID != 0 { + licenseInfoTopY += int32(licenseKeyDisplayH) + licenseInfoMargin + } + + // Center the message vertically between license info and note + availableSpace := p.note.Y - licenseInfoTopY - verticalMargin + p.message.Y = licenseInfoTopY + availableSpace/2 - int32(messageH/2) + } else { + // If no license info, place message below note + p.message.Y = p.note.Y - verticalMargin - int32(messageH) + } + p.message.X = p.Base.ScreenCenterX - int32(messageW/2) + // 8) Get shader and render textShader, ok := sm.Get("text") if !ok { @@ -295,17 +331,26 @@ func (p *Home) Render() error { p.Base.RenderTexture(textShader, p.title.ID, titlePos, titleDim, color) p.Base.RenderTexture(textShader, p.version.ID, versionPos, versionDim, color) - p.Base.RenderTexture(textShader, p.prompt.ID, p.prompt.Position(), promptDim, color) - p.Base.RenderTexture(textShader, p.note.ID, p.note.Position(), p.note.RawDimensions(), color) + p.Base.RenderTexture(textShader, p.prompt.ID, mgl32.Vec2{float32(p.prompt.X), float32(p.prompt.Y)}, promptDim, color) + p.Base.RenderTexture(textShader, p.note.ID, mgl32.Vec2{float32(p.note.X), float32(p.note.Y)}, mgl32.Vec2{noteW, noteH}, color) + + // Render message + if p.message.ID != 0 { + msgColor := colors.White + if p.isDownloadComplete { + msgColor = colors.Green + } + p.Base.RenderTexture(textShader, p.message.ID, mgl32.Vec2{float32(p.message.X), float32(p.message.Y)}, mgl32.Vec2{messageW, messageH}, msgColor) + } // Render license info if it exists if p.hasLicenseInfo { - p.Base.RenderTexture(textShader, p.licenseInfoHeading.ID, p.licenseInfoHeading.Position(), p.licenseInfoHeading.RawDimensions(), color) + p.Base.RenderTexture(textShader, p.licenseInfoHeading.ID, mgl32.Vec2{float32(p.licenseInfoHeading.X), float32(p.licenseInfoHeading.Y)}, mgl32.Vec2{licenseHeadingW, licenseHeadingH}, color) if p.emailDisplay.ID != 0 { - p.Base.RenderTexture(textShader, p.emailDisplay.ID, p.emailDisplay.Position(), p.emailDisplay.RawDimensions(), color) + p.Base.RenderTexture(textShader, p.emailDisplay.ID, mgl32.Vec2{float32(p.emailDisplay.X), float32(p.emailDisplay.Y)}, mgl32.Vec2{emailDisplayW, emailDisplayH}, color) } if p.licenseKeyDisplay.ID != 0 { - p.Base.RenderTexture(textShader, p.licenseKeyDisplay.ID, p.licenseKeyDisplay.Position(), p.licenseKeyDisplay.RawDimensions(), color) + p.Base.RenderTexture(textShader, p.licenseKeyDisplay.ID, mgl32.Vec2{float32(p.licenseKeyDisplay.X), float32(p.licenseKeyDisplay.Y)}, mgl32.Vec2{licenseKeyDisplayW, licenseKeyDisplayH}, color) } } @@ -318,6 +363,7 @@ func (p *Home) Destroy() error { gl.DeleteTextures(1, &p.version.ID) gl.DeleteTextures(1, &p.prompt.ID) gl.DeleteTextures(1, &p.note.ID) + gl.DeleteTextures(1, &p.message.ID) if p.hasLicenseInfo { gl.DeleteTextures(1, &p.licenseInfoHeading.ID) diff --git a/internal/page/router.go b/internal/page/router.go index 24c5ebd..eb8dda8 100644 --- a/internal/page/router.go +++ b/internal/page/router.go @@ -3,6 +3,7 @@ package page import ( "gorm.io/gorm" "rhemawavetech.com/rhemawave/internal/db" + "rhemawavetech.com/rhemawave/internal/download" ) type pageType int @@ -22,13 +23,14 @@ const ( ) type PageCtx struct { - CfgPtr *db.AppConfig - DbPtr *gorm.DB - W, H int32 - RefreshConfig func() - NavigateTo func(pageType) - Msg string // Message to be displayed by MessagePage - Inst string // Instructions to be displayed on MessagePage + CfgPtr *db.AppConfig + DbPtr *gorm.DB + W, H int32 + MessageChannel chan download.DownloadStatus + RefreshConfig func() + NavigateTo func(pageType) + Msg string // Message to be displayed by MessagePage + Inst string // Instructions to be displayed on MessagePage } type Router struct {