Files
radiantwave-legacy/internal/page/logViewerPage.go
T
NeilClack 17c088cc2f Update Database
Database is more usable now.
All pages have been updated to use the database, and share an application configuration.
Page Stack has been removed, no more pages hanging around in memory. Replaced with a 'Router' which uses an Enum to determine which page should be displayed, destroys the old page and initializes the new page all in one go. No more push, pop, rollback, etc. No more holding instances of pages.
Logger has been updated with proper timestamps and sorting now works as expected."
2026-03-23 15:02:07 -05:00

251 lines
6.7 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/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
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.Base.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")...)
// }
for _, entry := range logs {
logTime := time.UnixMilli(entry.Timestamp)
timeStr := logTime.Format("2006-01-02 15:04:05")
line := fmt.Sprintf("[%s] [%s] %s", timeStr, entry.Level, entry.Message)
p.lines = append(p.lines, line)
}
applicationFont := "Roboto-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.Base.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 {
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
sm := shaderManager.Get()
textShader, _ := sm.Get("text")
white := sdl.Color{R: 255, G: 255, B: 255, A: 255}
grey := sdl.Color{R: 180, G: 180, B: 180, A: 255}
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}, 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}, grey)
// 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
p.Base.RenderTexture(textShader, p.lineTextureIDs[i], mgl32.Vec2{float32(contentLeftX), float32(currentY)}, mgl32.Vec2{lineW, lineH}, white)
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()
}