Fix font scaling issue

This commit is contained in:
NeilClack
2026-03-25 13:53:05 -05:00
parent 678d679b53
commit 57691e6b68
2 changed files with 96 additions and 112 deletions
+1 -1
View File
@@ -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 {
+95 -111
View File
@@ -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
}