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."
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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/fontManager"
|
||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||
)
|
||||
|
||||
var GitVersion = "dev"
|
||||
|
||||
type Home struct {
|
||||
Base
|
||||
title StringItem
|
||||
version StringItem
|
||||
prompt StringItem
|
||||
note StringItem
|
||||
licenseInfoHeading StringItem
|
||||
emailDisplay StringItem
|
||||
licenseKeyDisplay StringItem
|
||||
displayStatus StringItem
|
||||
sdlDisplayStatus StringItem
|
||||
hasLicenseInfo bool
|
||||
lastDisplayUpdate time.Time
|
||||
drmDisplayCount int
|
||||
sdlDisplayCount int
|
||||
baseFontSize int // cached from db at init
|
||||
}
|
||||
|
||||
// countConnectedDisplays returns the number of connected displays via DRM
|
||||
func countConnectedDisplays() int {
|
||||
matches, err := filepath.Glob("/sys/class/drm/card*/status")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
connected := 0
|
||||
for _, path := range matches {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
status := strings.TrimSpace(string(content))
|
||||
if status == "connected" {
|
||||
connected++
|
||||
}
|
||||
}
|
||||
|
||||
return connected
|
||||
}
|
||||
|
||||
// countSDLDisplays returns the number of displays detected by SDL
|
||||
func countSDLDisplays() int {
|
||||
numDisplays, err := sdl.GetNumVideoDisplays()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return numDisplays
|
||||
}
|
||||
|
||||
// updateDisplayCounts updates the display count textures
|
||||
func (p *Home) updateDisplayCounts() error {
|
||||
var err error
|
||||
// Get font
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to fetch font")
|
||||
}
|
||||
|
||||
// Get base font size
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Update DRM count
|
||||
p.drmDisplayCount = countConnectedDisplays()
|
||||
|
||||
// Delete old texture
|
||||
if p.displayStatus.ID != 0 {
|
||||
gl.DeleteTextures(1, &p.displayStatus.ID)
|
||||
}
|
||||
|
||||
// Create new texture
|
||||
displayStatusText := fmt.Sprintf("Connected Displays: %d", p.drmDisplayCount)
|
||||
p.displayStatus, err = NewStringItem(displayStatusText, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create display status texture: %w", err)
|
||||
}
|
||||
|
||||
displayStatusScale := float32(24) / float32(baseFontSize)
|
||||
p.displayStatus.Scale(displayStatusScale)
|
||||
|
||||
// Update SDL count
|
||||
p.sdlDisplayCount = countSDLDisplays()
|
||||
|
||||
// Delete old texture
|
||||
if p.sdlDisplayStatus.ID != 0 {
|
||||
gl.DeleteTextures(1, &p.sdlDisplayStatus.ID)
|
||||
}
|
||||
|
||||
// Create new texture
|
||||
sdlDisplayStatusText := fmt.Sprintf("SDL Displays Detected: %d", p.sdlDisplayCount)
|
||||
p.sdlDisplayStatus, err = NewStringItem(sdlDisplayStatusText, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create SDL display status texture: %w", err)
|
||||
}
|
||||
|
||||
p.sdlDisplayStatus.Scale(displayStatusScale)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init performs basic page initialization and creation of static assets.
|
||||
func (p *Home) Init(ctx *PageCtx) error {
|
||||
|
||||
title := "Rhema Wave"
|
||||
version := GitVersion
|
||||
prompt := "Press F2 to Start, or F3 for Settings"
|
||||
note := "You can always press F1 to return here"
|
||||
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
// Get font
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to fetch font")
|
||||
}
|
||||
|
||||
// Create title texture
|
||||
var err error
|
||||
p.title, err = NewStringItem(title, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create title string texture: %w", err)
|
||||
}
|
||||
|
||||
// Create version texture
|
||||
versionString := "Version: " + version
|
||||
p.version, err = NewStringItem(versionString, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create version string texture: %w", err)
|
||||
}
|
||||
|
||||
// Create prompt texture
|
||||
p.prompt, err = NewStringItem(prompt, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create prompt string texture: %w", err)
|
||||
}
|
||||
|
||||
// Create note texture
|
||||
p.note, err = NewStringItem(note, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create note string texture: %w", err)
|
||||
}
|
||||
|
||||
// Initialize display counts
|
||||
p.lastDisplayUpdate = time.Now()
|
||||
if err := p.updateDisplayCounts(); err != nil {
|
||||
return fmt.Errorf("unable to update display counts: %w", err)
|
||||
}
|
||||
|
||||
// Create license info textures if data exists
|
||||
if p.Base.Context.CfgPtr.EmailAddress != "" || p.Base.Context.CfgPtr.LicenseKey != "" {
|
||||
p.hasLicenseInfo = true
|
||||
|
||||
// Create heading
|
||||
p.licenseInfoHeading, err = NewStringItem("License Info", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create license info heading texture: %w", err)
|
||||
}
|
||||
|
||||
// Create email display if exists
|
||||
if p.Base.Context.CfgPtr.EmailAddress != "" {
|
||||
emailText := fmt.Sprintf("Email: %s", p.Base.Context.CfgPtr.EmailAddress)
|
||||
p.emailDisplay, err = NewStringItem(emailText, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create email display texture: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create license key display if exists
|
||||
if p.Base.Context.CfgPtr.LicenseKey != "" {
|
||||
licenseKeyText := fmt.Sprintf("License Key: %s", p.Base.Context.CfgPtr.LicenseKey)
|
||||
p.licenseKeyDisplay, err = NewStringItem(licenseKeyText, font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create license key display texture: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get base font size from database
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
p.baseFontSize = baseFontSize
|
||||
|
||||
// Scale the note text
|
||||
fontSize := int32(32)
|
||||
scale := float32(fontSize) / float32(baseFontSize)
|
||||
p.note.Scale(scale * 0.75)
|
||||
|
||||
// Scale license info texts
|
||||
if p.hasLicenseInfo {
|
||||
licenseInfoScale := float32(28) / float32(baseFontSize)
|
||||
p.licenseInfoHeading.Scale(licenseInfoScale)
|
||||
if p.emailDisplay.ID != 0 {
|
||||
p.emailDisplay.Scale(licenseInfoScale * 0.85)
|
||||
}
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
p.licenseKeyDisplay.Scale(licenseInfoScale * 0.85)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleEvent processes SDL events and handles them appropriately for the Home page.
|
||||
func (p *Home) HandleEvent(event *sdl.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates animations and changing textures and other changes using `dt` which represents a time delta in float32.
|
||||
func (p *Home) Update(dt float32) error {
|
||||
// Update display counts every 2 seconds
|
||||
if time.Since(p.lastDisplayUpdate) >= 2*time.Second {
|
||||
if err := p.updateDisplayCounts(); err != nil {
|
||||
}
|
||||
p.lastDisplayUpdate = time.Now()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Home) Render() error {
|
||||
baseFontSize := p.baseFontSize
|
||||
sm := shaderManager.Get()
|
||||
verticalMargin := int32(20)
|
||||
|
||||
// 1) Calculate scales
|
||||
titleFontSize := int32(64)
|
||||
bodyFontSize := int32(32)
|
||||
|
||||
titleScale := float32(titleFontSize) / float32(baseFontSize)
|
||||
bodyScale := float32(bodyFontSize) / float32(baseFontSize)
|
||||
|
||||
// 2) Calculate dimensions (scaled)
|
||||
titleW := float32(p.title.W) * titleScale
|
||||
titleH := float32(p.title.H) * titleScale
|
||||
titleDim := mgl32.Vec2{titleW, titleH}
|
||||
|
||||
versionW := float32(p.version.W) * bodyScale
|
||||
versionH := float32(p.version.H) * bodyScale
|
||||
versionDim := mgl32.Vec2{versionW, versionH}
|
||||
|
||||
promptW := float32(p.prompt.W) * bodyScale
|
||||
promptH := float32(p.prompt.H) * bodyScale
|
||||
promptDim := mgl32.Vec2{promptW, promptH}
|
||||
|
||||
// p.note already scaled in Init via p.note.Scale(scale*0.75)
|
||||
noteDim := p.note.RawDimensions()
|
||||
noteW, noteH := noteDim[0], noteDim[1]
|
||||
|
||||
// 3) Calculate total height of the vertical stack
|
||||
totalH := int32(titleH) + verticalMargin +
|
||||
int32(versionH) + verticalMargin +
|
||||
int32(promptH) + verticalMargin +
|
||||
int32(noteH)
|
||||
|
||||
// 4) Y-up layout: start from the TOP of the centered stack and step DOWN
|
||||
top := p.Base.ScreenCenterY + totalH/2
|
||||
|
||||
titleY := top - int32(titleH) // Title sits at the top
|
||||
versionY := titleY - verticalMargin - int32(versionH)
|
||||
p.prompt.Y = versionY - verticalMargin - int32(promptH)
|
||||
p.note.Y = p.prompt.Y - verticalMargin - int32(noteH)
|
||||
|
||||
// 5) Center X for each item
|
||||
titleX := p.Base.ScreenCenterX - int32(titleW/2)
|
||||
versionX := p.Base.ScreenCenterX - int32(versionW/2)
|
||||
p.prompt.X = p.Base.ScreenCenterX - int32(promptW/2)
|
||||
p.note.X = p.Base.ScreenCenterX - int32(noteW/2)
|
||||
|
||||
titlePos := mgl32.Vec2{float32(titleX), float32(titleY)}
|
||||
versionPos := mgl32.Vec2{float32(versionX), float32(versionY)}
|
||||
|
||||
// 6) Position display status in bottom left corner
|
||||
displayStatusDim := p.displayStatus.RawDimensions()
|
||||
displayStatusH := displayStatusDim[1]
|
||||
|
||||
// sdlDisplayStatusDim := p.sdlDisplayStatus.RawDimensions()
|
||||
// sdlDisplayStatusH := sdlDisplayStatusDim[1]
|
||||
|
||||
displayStatusMargin := int32(5)
|
||||
|
||||
p.displayStatus.X = 15
|
||||
p.displayStatus.Y = 15
|
||||
|
||||
p.sdlDisplayStatus.X = 15
|
||||
p.sdlDisplayStatus.Y = 15 + int32(displayStatusH) + displayStatusMargin
|
||||
|
||||
// 7) Position license info at bottom center
|
||||
if p.hasLicenseInfo {
|
||||
licenseInfoMargin := int32(10)
|
||||
bottomMargin := int32(20)
|
||||
|
||||
// License info items are already scaled in Init
|
||||
licenseHeadingDim := p.licenseInfoHeading.RawDimensions()
|
||||
licenseHeadingW, licenseHeadingH := licenseHeadingDim[0], licenseHeadingDim[1]
|
||||
|
||||
var emailDisplayH, licenseKeyDisplayH float32
|
||||
if p.emailDisplay.ID != 0 {
|
||||
emailDisplayDim := p.emailDisplay.RawDimensions()
|
||||
emailDisplayH = emailDisplayDim[1]
|
||||
}
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
licenseKeyDisplayDim := p.licenseKeyDisplay.RawDimensions()
|
||||
licenseKeyDisplayH = licenseKeyDisplayDim[1]
|
||||
}
|
||||
|
||||
// Calculate total height of license info block
|
||||
licenseInfoTotalH := int32(licenseHeadingH)
|
||||
if p.emailDisplay.ID != 0 {
|
||||
licenseInfoTotalH += int32(emailDisplayH) + licenseInfoMargin
|
||||
}
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
licenseInfoTotalH += int32(licenseKeyDisplayH) + licenseInfoMargin
|
||||
}
|
||||
|
||||
// Start from bottom and work up
|
||||
currentY := bottomMargin
|
||||
|
||||
// License key (bottom-most if it exists)
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
licenseKeyDisplayDim := p.licenseKeyDisplay.RawDimensions()
|
||||
licenseKeyDisplayW := licenseKeyDisplayDim[0]
|
||||
p.licenseKeyDisplay.X = p.Base.ScreenCenterX - int32(licenseKeyDisplayW/2)
|
||||
p.licenseKeyDisplay.Y = currentY
|
||||
currentY += int32(licenseKeyDisplayH) + licenseInfoMargin
|
||||
}
|
||||
|
||||
// Email
|
||||
if p.emailDisplay.ID != 0 {
|
||||
emailDisplayDim := p.emailDisplay.RawDimensions()
|
||||
emailDisplayW := emailDisplayDim[0]
|
||||
p.emailDisplay.X = p.Base.ScreenCenterX - int32(emailDisplayW/2)
|
||||
p.emailDisplay.Y = currentY
|
||||
currentY += int32(emailDisplayH) + licenseInfoMargin
|
||||
}
|
||||
|
||||
// Heading (top-most)
|
||||
p.licenseInfoHeading.X = p.Base.ScreenCenterX - int32(licenseHeadingW/2)
|
||||
p.licenseInfoHeading.Y = currentY
|
||||
}
|
||||
|
||||
// 8) Get shader and render
|
||||
textShader, ok := sm.Get("text")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to fetch text shader")
|
||||
}
|
||||
color := colors.White
|
||||
|
||||
p.Base.RenderTexture(textShader, p.title.ID, titlePos, titleDim, color)
|
||||
p.Base.RenderTexture(textShader, p.version.ID, versionPos, versionDim, color)
|
||||
p.Base.RenderTexture(textShader, p.prompt.ID, p.prompt.Position(), promptDim, color)
|
||||
p.Base.RenderTexture(textShader, p.note.ID, p.note.Position(), p.note.RawDimensions(), color)
|
||||
|
||||
// Render display status
|
||||
p.Base.RenderTexture(textShader, p.displayStatus.ID, p.displayStatus.Position(), p.displayStatus.RawDimensions(), color)
|
||||
p.Base.RenderTexture(textShader, p.sdlDisplayStatus.ID, p.sdlDisplayStatus.Position(), p.sdlDisplayStatus.RawDimensions(), color)
|
||||
|
||||
// Render license info if it exists
|
||||
if p.hasLicenseInfo {
|
||||
p.Base.RenderTexture(textShader, p.licenseInfoHeading.ID, p.licenseInfoHeading.Position(), p.licenseInfoHeading.RawDimensions(), color)
|
||||
if p.emailDisplay.ID != 0 {
|
||||
p.Base.RenderTexture(textShader, p.emailDisplay.ID, p.emailDisplay.Position(), p.emailDisplay.RawDimensions(), color)
|
||||
}
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
p.Base.RenderTexture(textShader, p.licenseKeyDisplay.ID, p.licenseKeyDisplay.Position(), p.licenseKeyDisplay.RawDimensions(), color)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy cleans up page-specific resources.
|
||||
func (p *Home) Destroy() error {
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
gl.DeleteTextures(1, &p.version.ID)
|
||||
gl.DeleteTextures(1, &p.prompt.ID)
|
||||
gl.DeleteTextures(1, &p.note.ID)
|
||||
gl.DeleteTextures(1, &p.displayStatus.ID)
|
||||
gl.DeleteTextures(1, &p.sdlDisplayStatus.ID)
|
||||
|
||||
if p.hasLicenseInfo {
|
||||
gl.DeleteTextures(1, &p.licenseInfoHeading.ID)
|
||||
if p.emailDisplay.ID != 0 {
|
||||
gl.DeleteTextures(1, &p.emailDisplay.ID)
|
||||
}
|
||||
if p.licenseKeyDisplay.ID != 0 {
|
||||
gl.DeleteTextures(1, &p.licenseKeyDisplay.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return p.Base.Destroy()
|
||||
}
|
||||
Reference in New Issue
Block a user