Replace in-memory atomic AppConfig caching with direct DB queries via db.GetConfig across application and page layers. This simplifies state management and ensures all components always operate on the latest persisted configuration. Key changes: - Remove Application.Config atomic cache and refreshCache mechanism - Eliminate PageCtx.CfgPtr and pass DB handle instead - Replace all cfg reads with db.GetConfig(app.DB) - Update mixer, audio, UI pages, and initialization paths accordingly - Explicitly set AppConfig table name to avoid GORM pluralization issues - Reduce DB connection pooling (SetMaxIdleConns(1)) - Standardize application font to SpaceMonoNerdFont-Regular - Lower default audio volume (1.0 → 0.2) - Add Unicode checkmark glyph to font map Behavioral impact: - Configuration is no longer cached in memory - Every config access now hits the DB (more consistent, slightly more overhead)
275 lines
7.2 KiB
Go
275 lines
7.2 KiB
Go
package page
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/logger"
|
|
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
|
)
|
|
|
|
type LogViewerPage struct {
|
|
Base
|
|
|
|
titleTextureID uint32
|
|
titleTextureWidth int32
|
|
titleTextureHeight int32
|
|
|
|
lines []string
|
|
lineTextureIDs []uint32
|
|
lineTextureWidths []int32
|
|
lineTextureHeights []int32
|
|
lineColors []sdl.Color
|
|
|
|
hintTextureID uint32
|
|
hintTextureWidth int32
|
|
hintTextureHeight int32
|
|
|
|
scrollOffset int32
|
|
maxScrollOffset int32
|
|
lineHeight int32
|
|
visibleHeight int32
|
|
}
|
|
|
|
func (p *LogViewerPage) Init(ctx *PageCtx) error {
|
|
var logs []db.Log
|
|
if err := p.Base.Init(ctx); err != nil {
|
|
return fmt.Errorf("failed base page initialization: %w", err)
|
|
}
|
|
|
|
if result := p.Context.DbPtr.Order("timestamp desc").Limit(250).Find(&logs); result.Error != nil {
|
|
logger.Error("error querying database for logs: %s", result.Error)
|
|
return fmt.Errorf("%s", result.Error)
|
|
}
|
|
|
|
// // Format and word-wrap each log entry
|
|
// for _, entry := range logs {
|
|
// formatted := fmt.Sprintf("[%s] [%s] %s", entry.Timestamp, entry.Level, entry.Message)
|
|
// wrapped := wrapText(formatted, 100)
|
|
// p.lines = append(p.lines, strings.Split(wrapped, "\n")...)
|
|
// }
|
|
|
|
levelColors := map[string]sdl.Color{
|
|
"DEBUG": colors.SkyBlue,
|
|
"INFO": colors.White,
|
|
"WARNING": colors.Yellow,
|
|
"ERROR": colors.RedOrange,
|
|
}
|
|
|
|
fallbackColor := colors.White
|
|
|
|
p.lineColors = make([]sdl.Color, len(p.lines))
|
|
|
|
for _, entry := range logs {
|
|
color, ok := levelColors[entry.Level]
|
|
if !ok {
|
|
color = fallbackColor
|
|
}
|
|
logTime := time.UnixMilli(entry.Timestamp)
|
|
timeStr := logTime.Format("2006-01-02 15:04")
|
|
line := fmt.Sprintf("[%s] [%s] %s", timeStr, entry.Level, entry.Message)
|
|
p.lines = append(p.lines, line)
|
|
p.lineColors = append(p.lineColors, color)
|
|
}
|
|
|
|
applicationFont := "SpaceMonoNerdFont-Regular"
|
|
|
|
fm := fontManager.Get()
|
|
textShader, ok := shaderManager.Get().Get("text")
|
|
if !ok {
|
|
return fmt.Errorf("unable to fetch text shader")
|
|
}
|
|
|
|
// Title texture
|
|
titleID, titleW, titleH, err := fm.CreateStringTexture(applicationFont, "Application Logs", textShader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create title texture: %w", err)
|
|
}
|
|
p.titleTextureID = titleID
|
|
p.titleTextureWidth = titleW
|
|
p.titleTextureHeight = titleH
|
|
|
|
// Line textures
|
|
p.lineTextureIDs = make([]uint32, len(p.lines))
|
|
p.lineTextureWidths = make([]int32, len(p.lines))
|
|
p.lineTextureHeights = make([]int32, len(p.lines))
|
|
|
|
for i, line := range p.lines {
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
texID, w, h, err := fm.CreateStringTexture(applicationFont, line, textShader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create line texture: %w", err)
|
|
}
|
|
p.lineTextureIDs[i] = texID
|
|
p.lineTextureWidths[i] = w
|
|
p.lineTextureHeights[i] = h
|
|
}
|
|
|
|
// Hint texture
|
|
hintText := "Arrow Keys / PgUp/PgDn to scroll | F1|2|3 to Exit"
|
|
hintID, hintW, hintH, err := fm.CreateStringTexture(applicationFont, hintText, textShader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create hint texture: %w", err)
|
|
}
|
|
p.hintTextureID = hintID
|
|
p.hintTextureWidth = hintW
|
|
p.hintTextureHeight = hintH
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *LogViewerPage) HandleEvent(event *sdl.Event) error {
|
|
switch e := (*event).(type) {
|
|
case *sdl.KeyboardEvent:
|
|
if e.Type == sdl.KEYDOWN {
|
|
switch e.Keysym.Sym {
|
|
case sdl.K_BACKSPACE:
|
|
p.Context.NavigateTo(SETTINGS)
|
|
case sdl.K_UP:
|
|
p.scrollOffset -= p.lineHeight
|
|
if p.scrollOffset < 0 {
|
|
p.scrollOffset = 0
|
|
}
|
|
case sdl.K_DOWN:
|
|
p.scrollOffset += p.lineHeight
|
|
if p.scrollOffset > p.maxScrollOffset {
|
|
p.scrollOffset = p.maxScrollOffset
|
|
}
|
|
case sdl.K_PAGEUP:
|
|
p.scrollOffset -= p.visibleHeight
|
|
if p.scrollOffset < 0 {
|
|
p.scrollOffset = 0
|
|
}
|
|
case sdl.K_PAGEDOWN:
|
|
p.scrollOffset += p.visibleHeight
|
|
if p.scrollOffset > p.maxScrollOffset {
|
|
p.scrollOffset = p.maxScrollOffset
|
|
}
|
|
case sdl.K_HOME:
|
|
p.scrollOffset = 0
|
|
case sdl.K_END:
|
|
p.scrollOffset = p.maxScrollOffset
|
|
}
|
|
}
|
|
case *sdl.MouseWheelEvent:
|
|
p.scrollOffset -= e.Y * p.lineHeight * 3
|
|
if p.scrollOffset < 0 {
|
|
p.scrollOffset = 0
|
|
}
|
|
if p.scrollOffset > p.maxScrollOffset {
|
|
p.scrollOffset = p.maxScrollOffset
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *LogViewerPage) Update(dt float32) error {
|
|
return nil
|
|
}
|
|
|
|
func (p *LogViewerPage) Render() error {
|
|
config, err := db.GetConfig(p.Context.DbPtr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
baseFontSize := config.StandardFontSize
|
|
|
|
sm := shaderManager.Get()
|
|
textShader, _ := sm.Get("text")
|
|
|
|
titleFontSize := int32(48)
|
|
titleScale := float32(titleFontSize) / float32(baseFontSize)
|
|
contentFontSize := int32(24)
|
|
contentScale := float32(contentFontSize) / float32(baseFontSize)
|
|
hintFontSize := int32(20)
|
|
hintScale := float32(hintFontSize) / float32(baseFontSize)
|
|
|
|
margin := int32(40)
|
|
lineSpacing := int32(4)
|
|
p.lineHeight = contentFontSize + lineSpacing
|
|
|
|
contentColumnWidth := int32(float32(p.Base.ScreenWidth) * 0.55)
|
|
contentLeftX := p.Base.ScreenCenterX - contentColumnWidth/2
|
|
|
|
// Title
|
|
titleH := float32(p.titleTextureHeight) * titleScale
|
|
titleW := float32(p.titleTextureWidth) * titleScale
|
|
titleX := p.Base.ScreenCenterX - int32(titleW/2)
|
|
titleY := p.Base.ScreenHeight - margin - int32(titleH)
|
|
p.Base.RenderTexture(textShader, p.titleTextureID, mgl32.Vec2{float32(titleX), float32(titleY)}, mgl32.Vec2{titleW, titleH}, colors.White)
|
|
|
|
// Hint at bottom
|
|
hintH := float32(p.hintTextureHeight) * hintScale
|
|
hintW := float32(p.hintTextureWidth) * hintScale
|
|
hintX := p.Base.ScreenCenterX - int32(hintW/2)
|
|
hintY := margin
|
|
p.Base.RenderTexture(textShader, p.hintTextureID, mgl32.Vec2{float32(hintX), float32(hintY)}, mgl32.Vec2{hintW, hintH}, colors.Gray)
|
|
|
|
// Content area bounds
|
|
contentTop := titleY - margin
|
|
contentBottom := hintY + int32(hintH) + margin
|
|
p.visibleHeight = contentTop - contentBottom
|
|
|
|
// Total content height
|
|
totalContentHeight := int32(len(p.lines)) * p.lineHeight
|
|
p.maxScrollOffset = totalContentHeight - p.visibleHeight
|
|
if p.maxScrollOffset < 0 {
|
|
p.maxScrollOffset = 0
|
|
}
|
|
|
|
// Render visible lines
|
|
currentY := contentTop - p.lineHeight + p.scrollOffset
|
|
|
|
for i, line := range p.lines {
|
|
if strings.TrimSpace(line) == "" {
|
|
currentY -= p.lineHeight
|
|
continue
|
|
}
|
|
|
|
if p.lineTextureIDs[i] == 0 {
|
|
currentY -= p.lineHeight
|
|
continue
|
|
}
|
|
|
|
if currentY > contentTop || currentY+p.lineHeight < contentBottom {
|
|
currentY -= p.lineHeight
|
|
continue
|
|
}
|
|
|
|
lineW := float32(p.lineTextureWidths[i]) * contentScale
|
|
lineH := float32(p.lineTextureHeights[i]) * contentScale
|
|
|
|
color := colors.White
|
|
if i < len(p.lineColors) {
|
|
color = p.lineColors[i]
|
|
}
|
|
|
|
p.Base.RenderTexture(textShader, p.lineTextureIDs[i], mgl32.Vec2{float32(contentLeftX), float32(currentY)}, mgl32.Vec2{lineW, lineH}, color)
|
|
currentY -= p.lineHeight
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *LogViewerPage) Destroy() error {
|
|
gl.DeleteTextures(1, &p.titleTextureID)
|
|
gl.DeleteTextures(1, &p.hintTextureID)
|
|
|
|
for _, texID := range p.lineTextureIDs {
|
|
if texID != 0 {
|
|
gl.DeleteTextures(1, &texID)
|
|
}
|
|
}
|
|
|
|
return p.Base.Destroy()
|
|
}
|