274 lines
7.1 KiB
Go
274 lines
7.1 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"
|
|
"radiantwavetech.com/radiantwave/internal/colors"
|
|
"radiantwavetech.com/radiantwave/internal/db"
|
|
"radiantwavetech.com/radiantwave/internal/fontManager"
|
|
"radiantwavetech.com/radiantwave/internal/logger"
|
|
"radiantwavetech.com/radiantwave/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
|
|
|
|
contentLeftX := 25
|
|
|
|
// 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()
|
|
}
|