diff --git a/internal/application/application.go b/internal/application/application.go index bb49cb1..d45edd4 100644 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -28,7 +28,7 @@ const ( volumeStepSize = 16 openGLMajorVersion = 3 openGLMinorVersion = 3 - apiURL = "http://localhost:5173/api/v1" + apiURL = "https://portal.rhemawavetech.com/api/v1" ) type Application struct { diff --git a/internal/page/scrollerPage.go b/internal/page/scrollerPage.go index 9068dde..18e6ef5 100644 --- a/internal/page/scrollerPage.go +++ b/internal/page/scrollerPage.go @@ -67,7 +67,6 @@ import ( "rhemawavetech.com/rhemawave/internal/colors" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" - "rhemawavetech.com/rhemawave/internal/logger" "rhemawavetech.com/rhemawave/internal/mixer" colorPattern "rhemawavetech.com/rhemawave/internal/pattern/color" linePattern "rhemawavetech.com/rhemawave/internal/pattern/line" @@ -169,7 +168,6 @@ type ScrollerPage struct { wordIndex int lines *LineDeque lineHeight int32 - fontScale float32 shader *shaderManager.Shader activePattern linePattern.LinePattern font *fontManager.FontEntry @@ -178,6 +176,7 @@ type ScrollerPage struct { colorPattern *colorPattern.ColorPattern scrollOffset float32 scrollY float32 + fontScale float32 increment float32 velocity float32 @@ -195,78 +194,80 @@ type ScrollerPage struct { } func (p *ScrollerPage) Init(ctx *PageCtx) error { - // 1. Basic configuration and Deque allocation - // We do this first so Destroy() has a valid object to clean up if we fail later. p.increment = 100.0 p.velocity = 800.0 p.SetAutoVelocity(true, -4000.0, 4000.0, 100.0, 2.5, 6.0) - standardFontSize := ctx.CfgPtr.StandardFontSize - baseFontSize := ctx.CfgPtr.StandardFontSize - p.fontScale = float32(standardFontSize) / float32(baseFontSize) - - nLines := int(p.ScreenHeight/int32(standardFontSize)) + 5 - p.lines = NewLineDeque(nLines * 2) - - // 2. Initialize Base and Managers - p.Base.Init(ctx) sm := shaderManager.Get() fm := fontManager.Get() - if fm == nil || sm == nil { - return fmt.Errorf("critical systems (font/shader managers) not initialized") - } - // 3. Resource Loading: Font + // Get font font, ok := fm.GetFont("Scrambled") - if !ok || font == nil { - return fmt.Errorf("unable to fetch font 'Scrambled': font is nil") + if !ok { + return fmt.Errorf("unable to fetch font 'Scrambled'") } p.font = font - // 4. Resource Loading: Shader + p.Base.Init(ctx) + + // Get text shader textShader, ok := sm.Get("text") - if !ok || textShader == nil { - return fmt.Errorf("text shader not found or is nil") + if !ok { + return fmt.Errorf("text shader not found") } p.shader = textShader - // 5. Calculate Font Metrics (Now safe because p.font is guaranteed) - spaceGlyph, ok := fm.GetGlyph(p.font.Name, ' ') - if !ok { - return fmt.Errorf("failed to get space glyph for font: %s", p.font.Name) - } - p.spaceWidth = int32(float32(spaceGlyph.Advance) * p.fontScale) - - // Get line height (Using a temporary texture then immediate cleanup) - tmpStrID, _, H, err := fm.NewCreateStringTexture(p.font.Name, "Mg", p.shader) - if err != nil { - return fmt.Errorf("failed to create sample texture for metrics: %w", err) - } - p.lineHeight = int32(float32(H) * p.fontScale) - gl.DeleteTextures(1, &tmpStrID) - - // 6. Load Data: Proclamations + // Load proclamation words from database if err := p.loadProclamations(); err != nil { - // If no proclamations, we must stop here or we'll crash in createLine - p.Context.NavigateTo(PROCLAMATIONS) - return fmt.Errorf("navigation triggered: %w", err) + // Don't stop execution, just show fallback words + p.words = strings.Fields("No Proclamations Selected!") } p.wordIndex = 0 - // 7. Load Data: Patterns + // Get line pattern from database + var err error linePatternName := p.Base.Context.CfgPtr.LinePattern + p.activePattern, err = linePattern.Get(linePatternName) if err != nil { return fmt.Errorf("failed to load line pattern '%s': %w", linePatternName, err) } + // Initialize color pattern var baseColors = []sdl.Color{ - colors.Tan, colors.PeachPuff, colors.LightCyan, - colors.LightSeaGreen, colors.MediumTurquoise, - colors.DarkCyan, colors.MediumPurple, + colors.Tan, + colors.PeachPuff, + colors.LightCyan, + colors.LightSeaGreen, + colors.MediumTurquoise, + colors.DarkCyan, + colors.MediumPurple, } p.colorPattern = colorPattern.NewColorPattern(baseColors) + // Get font sizes from database (optimize: load once, cache as int32) + // Match the working page's logic exactly: + p.fontScale = float32(32) / float32(p.Base.Context.CfgPtr.StandardFontSize) + + // Font setup + nLines := int(float32(p.ScreenHeight) / 32.0) // 32 is your target visual size + p.lines = NewLineDeque(nLines * 2) + + // Calculate space width using the SCALE ratio, not the font size int + spaceGlyph, ok := fm.GetGlyph(p.font.Name, ' ') + if !ok { + return fmt.Errorf("failed to get space glyph") + } + p.spaceWidth = int32(float32(spaceGlyph.Advance) * p.fontScale) + + // Get line height using the SCALE ratio + tmpStrID, _, H, err := fm.NewCreateStringTexture(p.font.Name, "Mg", p.shader) + if err != nil { + return fmt.Errorf("failed to create sample texture: %w", err) + } + p.lineHeight = int32(float32(H) * p.fontScale) + gl.DeleteTextures(1, &tmpStrID) + p.lineCtx = &linePattern.LineRenderContext{ ScreenWidth: p.ScreenWidth, Font: p.font, @@ -274,23 +275,37 @@ func (p *ScrollerPage) Init(ctx *PageCtx) error { SpaceWidth: p.spaceWidth, } - // 8. Generate Initial Visual State + // Create initial lines for i := 0; i < nLines; i++ { if err := p.createLine(i, true); err != nil { return fmt.Errorf("failed to create initial line %d: %w", i, err) } } - // 9. Audio Initialization + // Audio setup audioDeviceName := p.Base.Context.CfgPtr.SelectedAudioDevice + + // Switch audio devices if needed if mixer.CurrentDevice() != audioDeviceName { - _ = mixer.SwitchDevice(audioDeviceName) + if err := mixer.SwitchDevice(audioDeviceName); err != nil { + } else { + } } + // Start audio playback selectedAudio := p.Base.Context.CfgPtr.SelectedAudio + assetsDir := p.Base.Context.CfgPtr.AssetsDir - if selectedAudio != "" { - _ = mixer.Play(filepath.Join(assetsDir, selectedAudio), true, p.Base.Context.CfgPtr) + + if selectedAudio == "" { + } else { + if err := mixer.Play(filepath.Join(assetsDir, selectedAudio), true, p.Base.Context.CfgPtr); err != nil { + } + } + + currentDevice := mixer.CurrentDevice() + if currentDevice == "" { + currentDevice = "default" } return nil @@ -300,26 +315,22 @@ func (p *ScrollerPage) Init(ctx *PageCtx) error { func (p *ScrollerPage) loadProclamations() error { p.words = []string{} - // 1. Query only Selected AND Available records directly from the DB + // Get all selected proclamations from database var proclamations []db.Proclamation - // TODO: Uncomment and replace this line once available is added - // err := p.Base.Context.DbPtr.Where("selected = ? AND available = ?", true, true).Find(&proclamations).Error - - err := p.Base.Context.DbPtr.Where("selected = ?", true).Find(&proclamations).Error - + err := p.Context.DbPtr.Where("selected = ?", true).Find(&proclamations).Error if err != nil { - logger.Error("Database query failed: %v", err) return err } - // 2. Build word list + // Build word list from selected, available proclamations for _, proclamation := range proclamations { - // strings.Fields handles multiple spaces and newlines automatically - words := strings.Fields(proclamation.Content) - p.words = append(p.words, words...) + if proclamation.Selected && proclamation.Available { + // Split content into words and add to list + words := strings.Fields(proclamation.Content) + p.words = append(p.words, words...) + } } - // 3. Handle empty state if len(p.words) == 0 { p.words = strings.Fields("No Proclamations Selected!") return fmt.Errorf("no proclamations selected or available") @@ -343,8 +354,8 @@ func (p *ScrollerPage) createLine(index int, isInitial bool) error { if err != nil { return fmt.Errorf("unable to create string item for word '%s': %w", word, err) } - item.W = int32(float32(item.W) * p.fontScale) - item.H = int32(float32(item.H) * p.fontScale) + + item.Scale(p.fontScale) if len(line.words) > 0 { line.width += p.spaceWidth @@ -477,8 +488,8 @@ func (p *ScrollerPage) addLineAtBottom() error { if err != nil { return fmt.Errorf("unable to create string item for word '%s': %w", word, err) } - item.W = int32(float32(item.W) * p.fontScale) - item.H = int32(float32(item.H) * p.fontScale) + + item.Scale(p.fontScale) if len(line.words) > 0 { line.width += p.spaceWidth @@ -523,8 +534,8 @@ func (p *ScrollerPage) addLineAtTop() error { if err != nil { return fmt.Errorf("unable to create string item for word '%s': %w", word, err) } - item.W = int32(float32(item.W) * p.fontScale) - item.H = int32(float32(item.H) * p.fontScale) + + item.Scale(p.fontScale) if len(line.words) > 0 { line.width += p.spaceWidth @@ -571,61 +582,34 @@ func (p *ScrollerPage) Render() error { return nil } -// Drop references func (p *ScrollerPage) Destroy() error { if p == nil { return nil } - // 1. Audio Guard - // Ensure Context and CfgPtr exist before stopping the mixer - if p.Context != nil && p.Context.CfgPtr != nil { - mixer.Stop(1000, p.Context.CfgPtr) - } + mixer.Stop(1000, p.Base.Context.CfgPtr) - // 2. Lines / OpenGL Guard - // We only iterate and delete textures if the deque was actually allocated - if p.lines != nil { - for i := 0; i < p.lines.Len(); i++ { - line := p.lines.Get(i) - for j := range line.words { - // Double-check word ID to avoid redundant GL calls - if line.words[j].ID != 0 { - gl.DeleteTextures(1, &line.words[j].ID) - line.words[j].ID = 0 - } + // GL cleanup — delete all text textures + for i := 0; i < p.lines.Len(); i++ { + line := p.lines.Get(i) + for j := range line.words { + if id := line.words[j].ID; id != 0 { + gl.DeleteTextures(1, &line.words[j].ID) + line.words[j].ID = 0 } } - p.lines.Clear() // Optional: clear circular buffer internal refs - p.lines = nil } - // 3. Pointer Cleanup Guards - // Explicitly setting to nil to help the Garbage Collector, - // but only if they aren't already nil. - if p.words != nil { - p.words = nil - } - - if p.lineCtx != nil { - p.lineCtx = nil - } - - if p.font != nil { - p.font = nil - } - - if p.shader != nil { - p.shader = nil - } - - if p.colorPattern != nil { - p.colorPattern = nil - } - - // Final GPU sync gl.Flush() + // Drop references for GC + p.lines = nil + p.words = nil + p.lineCtx = nil + p.font = nil + p.shader = nil + p.colorPattern = nil + return nil }