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:
@@ -2,10 +2,10 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"gorm.io/gorm"
|
||||
"rhemawavetech.com/rhemawave/internal/colors"
|
||||
"rhemawavetech.com/rhemawave/internal/db"
|
||||
"rhemawavetech.com/rhemawave/internal/fontManager"
|
||||
@@ -24,9 +24,11 @@ type AudioDevices struct {
|
||||
highlightIndex int
|
||||
selectedIndex int
|
||||
verticalMargin int32
|
||||
CfgPtr *db.AppConfig
|
||||
dbPtr *gorm.DB
|
||||
}
|
||||
|
||||
func (p *AudioDevices) Init(app ApplicationInterface) error {
|
||||
func (p *AudioDevices) Init(ctx *PageCtx) error {
|
||||
var err error
|
||||
p.verticalMargin = int32(20)
|
||||
|
||||
@@ -50,19 +52,12 @@ func (p *AudioDevices) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Initialize base
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("base init failed: %w", err)
|
||||
}
|
||||
|
||||
// Get font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size %q: %w", baseFontSizeStr, err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Calculate scales
|
||||
titleScale := float32(64) / float32(baseFontSize)
|
||||
@@ -90,10 +85,7 @@ func (p *AudioDevices) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Get currently selected device from database
|
||||
currentDevice, err := db.GetConfigValue("audio_device_name")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving audio_device_name from db: %w", err)
|
||||
}
|
||||
currentDevice := p.Base.Context.CfgPtr.SelectedAudioDevice
|
||||
|
||||
// Create UI items for each device
|
||||
for i, name := range devices { // Iterate over the devices
|
||||
@@ -111,13 +103,10 @@ func (p *AudioDevices) Init(app ApplicationInterface) error {
|
||||
}
|
||||
}
|
||||
|
||||
on, err := db.GetConfigValue("audio_enabled")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to query database for audio_enabled: %w", err)
|
||||
}
|
||||
on := p.Base.Context.CfgPtr.AudioEnabled
|
||||
|
||||
audioStatus := "Turn Audio On"
|
||||
if on == "true" {
|
||||
if on {
|
||||
audioStatus = "Turn Audio Off"
|
||||
}
|
||||
|
||||
@@ -177,22 +166,11 @@ func (p *AudioDevices) HandleEvent(event *sdl.Event) error {
|
||||
|
||||
// Handle audio disable
|
||||
if selection == "Turn Audio Off" || selection == "Turn Audio On" {
|
||||
on, err := db.GetConfigValue("audio_enabled")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch audio_enabled from database: %w", err)
|
||||
}
|
||||
|
||||
currentStatus, err := strconv.ParseBool(on)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse audio_enabled value: %w", err)
|
||||
}
|
||||
|
||||
newStatus := strconv.FormatBool(!currentStatus)
|
||||
if err := db.SetConfigValue("audio_enabled", newStatus); err != nil {
|
||||
return fmt.Errorf("failed to update audio_enabled: %w", err)
|
||||
}
|
||||
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
newStatus := !p.Base.Context.CfgPtr.AudioEnabled
|
||||
updatedConfig := p.Base.Context.CfgPtr
|
||||
updatedConfig.AudioEnabled = newStatus
|
||||
p.Base.Context.DbPtr.Save(&updatedConfig)
|
||||
p.Base.Context.RefreshConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -208,17 +186,12 @@ func (p *AudioDevices) HandleEvent(event *sdl.Event) error {
|
||||
}
|
||||
|
||||
// Save settings
|
||||
if err := db.SetConfigValue("audio_device_name", deviceName); err != nil {
|
||||
return fmt.Errorf("failed to save audio device: %w", err)
|
||||
}
|
||||
if err := db.SetConfigValue("audio_enabled", "true"); err != nil {
|
||||
return fmt.Errorf("failed to enable audio: %w", err)
|
||||
}
|
||||
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
|
||||
p.Base.Context.CfgPtr.SelectedAudioDevice = deviceName
|
||||
p.Base.Context.CfgPtr.AudioEnabled = true
|
||||
p.Base.Context.DbPtr.Save(p.Base.Context.CfgPtr)
|
||||
p.Base.Context.RefreshConfig()
|
||||
case sdl.K_BACKSPACE:
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Base.Context.NavigateTo(SETTINGS)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"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/keybinds"
|
||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||
)
|
||||
|
||||
type downloadState int
|
||||
|
||||
const (
|
||||
downloadStateDownloading downloadState = iota
|
||||
downloadStateFinished
|
||||
)
|
||||
|
||||
// DownloadProgressPage displays a progress bar while an audio file is downloading.
|
||||
// The page is inescapable: no input is accepted while downloading, and only
|
||||
// Enter is accepted once the download finishes.
|
||||
type DownloadProgressPage struct {
|
||||
Base
|
||||
textShader *shaderManager.Shader
|
||||
solidShader *shaderManager.Shader
|
||||
|
||||
// Set by the application before pushing this page.
|
||||
TotalBytes *atomic.Int64
|
||||
BytesWritten *atomic.Int64
|
||||
Done *atomic.Bool
|
||||
HasError *atomic.Bool
|
||||
NextPage Page // where to navigate on success
|
||||
|
||||
state downloadState
|
||||
title StringItem
|
||||
prompt StringItem
|
||||
percentText StringItem
|
||||
lastPercent int
|
||||
|
||||
barX, barY, barW, barH int32
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Init(app ApplicationInterface) error {
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
fm := fontManager.Get()
|
||||
sm := shaderManager.Get()
|
||||
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get font")
|
||||
}
|
||||
|
||||
p.textShader, ok = sm.Get("text")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get text shader")
|
||||
}
|
||||
p.solidShader, ok = sm.Get("solid_color")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get solid_color shader")
|
||||
}
|
||||
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
|
||||
titleScale := float32(48) / float32(baseFontSize)
|
||||
p.title, err = NewStringItem("Downloading Audio...", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating title: %w", err)
|
||||
}
|
||||
p.title.Scale(titleScale)
|
||||
|
||||
promptScale := float32(32) / float32(baseFontSize)
|
||||
p.prompt, err = NewStringItem("Press Enter to continue", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating prompt: %w", err)
|
||||
}
|
||||
p.prompt.Scale(promptScale)
|
||||
|
||||
p.percentText, err = NewStringItem("0%", font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating percent text: %w", err)
|
||||
}
|
||||
p.percentText.Scale(promptScale)
|
||||
p.lastPercent = 0
|
||||
|
||||
// Progress bar dimensions: 60% of screen width, 30px tall, centered
|
||||
p.barW = int32(float32(p.ScreenWidth) * 0.6)
|
||||
p.barH = 30
|
||||
p.barX = p.ScreenCenterX - p.barW/2
|
||||
p.barY = p.ScreenCenterY - p.barH/2
|
||||
|
||||
// Position title above bar
|
||||
p.title.X = p.ScreenCenterX - p.title.W/2
|
||||
p.title.Y = p.barY + p.barH + 40
|
||||
|
||||
p.state = downloadStateDownloading
|
||||
keybinds.Enabled = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Update(dt float32) error {
|
||||
if p.state == downloadStateFinished {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Done.Load() {
|
||||
p.state = downloadStateFinished
|
||||
|
||||
// Force progress to 100% on success
|
||||
if !p.HasError.Load() && p.TotalBytes.Load() > 0 {
|
||||
p.BytesWritten.Store(p.TotalBytes.Load())
|
||||
p.lastPercent = 100
|
||||
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if ok {
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
percentScale := float32(32) / float32(baseFontSize)
|
||||
newText, err := NewStringItem("100%", font, colors.White)
|
||||
if err == nil {
|
||||
newText.Scale(percentScale)
|
||||
p.percentText = newText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update title to reflect result
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
titleScale := float32(48) / float32(baseFontSize)
|
||||
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
|
||||
var titleText string
|
||||
if p.HasError.Load() {
|
||||
titleText = "Download Failed"
|
||||
} else {
|
||||
titleText = "Download Complete"
|
||||
}
|
||||
newTitle, err := NewStringItem(titleText, font, colors.White)
|
||||
if err == nil {
|
||||
newTitle.Scale(titleScale)
|
||||
p.title = newTitle
|
||||
p.title.X = p.ScreenCenterX - p.title.W/2
|
||||
p.title.Y = p.barY + p.barH + 40
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update percentage texture when it changes
|
||||
percent := 0
|
||||
if p.TotalBytes.Load() > 0 {
|
||||
percent = int(p.BytesWritten.Load() * 100 / p.TotalBytes.Load())
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
}
|
||||
|
||||
if percent != p.lastPercent {
|
||||
p.lastPercent = percent
|
||||
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
|
||||
baseFontSizeStr, _ := db.GetConfigValue("init_font_size")
|
||||
baseFontSize, _ := strconv.Atoi(baseFontSizeStr)
|
||||
percentScale := float32(32) / float32(baseFontSize)
|
||||
|
||||
newText, err := NewStringItem(fmt.Sprintf("%d%%", percent), font, colors.White)
|
||||
if err == nil {
|
||||
newText.Scale(percentScale)
|
||||
p.percentText = newText
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) HandleEvent(event *sdl.Event) error {
|
||||
// Inescapable while downloading
|
||||
if p.state != downloadStateFinished {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch e := (*event).(type) {
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type != sdl.KEYDOWN {
|
||||
return nil
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_RETURN {
|
||||
if p.HasError.Load() || p.NextPage == nil {
|
||||
p.App.SchedulePop()
|
||||
} else {
|
||||
p.App.SwitchPage(p.NextPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Render() error {
|
||||
// Render title
|
||||
p.RenderTexture(p.textShader, p.title.ID, p.title.Position(), p.title.RawDimensions(), p.title.Color)
|
||||
|
||||
// Progress bar background (dark gray)
|
||||
bgColor := sdl.Color{R: 60, G: 60, B: 60, A: 255}
|
||||
barPos := mgl32.Vec2{float32(p.barX), float32(p.barY)}
|
||||
barDim := mgl32.Vec2{float32(p.barW), float32(p.barH)}
|
||||
p.RenderSolidColorQuad(p.solidShader, barPos, barDim, bgColor)
|
||||
|
||||
// Progress bar foreground
|
||||
progress := float32(0)
|
||||
if p.TotalBytes.Load() > 0 {
|
||||
progress = float32(p.BytesWritten.Load()) / float32(p.TotalBytes.Load())
|
||||
if progress > 1.0 {
|
||||
progress = 1.0
|
||||
}
|
||||
}
|
||||
if progress > 0 {
|
||||
fgColor := sdl.Color{R: 255, G: 255, B: 255, A: 255}
|
||||
fgDim := mgl32.Vec2{float32(p.barW) * progress, float32(p.barH)}
|
||||
p.RenderSolidColorQuad(p.solidShader, barPos, fgDim, fgColor)
|
||||
}
|
||||
|
||||
// Render percentage text below bar
|
||||
p.percentText.X = p.ScreenCenterX - p.percentText.W/2
|
||||
p.percentText.Y = p.barY - p.percentText.H - 20
|
||||
p.RenderTexture(p.textShader, p.percentText.ID, p.percentText.Position(), p.percentText.RawDimensions(), p.percentText.Color)
|
||||
|
||||
// Show prompt when finished
|
||||
if p.state == downloadStateFinished {
|
||||
p.prompt.X = p.ScreenCenterX - p.prompt.W/2
|
||||
p.prompt.Y = p.barY - p.percentText.H - 20 - p.prompt.H - 20
|
||||
p.RenderTexture(p.textShader, p.prompt.ID, p.prompt.Position(), p.prompt.RawDimensions(), p.prompt.Color)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DownloadProgressPage) Destroy() error {
|
||||
keybinds.Enabled = true
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
gl.DeleteTextures(1, &p.prompt.ID)
|
||||
gl.DeleteTextures(1, &p.percentText.ID)
|
||||
return nil
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package page
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"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/shaderManager"
|
||||
)
|
||||
@@ -53,19 +51,15 @@ func isValidEmail(email string) bool {
|
||||
}
|
||||
|
||||
// Init performs basic page initialization and creation of static assets.
|
||||
func (p *EmailAddressPage) Init(app ApplicationInterface) error {
|
||||
func (p *EmailAddressPage) Init(ctx *PageCtx) error {
|
||||
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
p.title = "Your Email Address"
|
||||
|
||||
// Get application font from database
|
||||
applicationFont, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFont := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
textShader, ok := shaderManager.Get().Get("text")
|
||||
@@ -87,10 +81,7 @@ func (p *EmailAddressPage) Init(app ApplicationInterface) error {
|
||||
p.titleTextureHeight = titleHeight
|
||||
|
||||
// Load existing email address from database
|
||||
emailAddress, err := db.GetConfigValue("email_address")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving email_address from db: %w", err)
|
||||
}
|
||||
emailAddress := ctx.CfgPtr.EmailAddress
|
||||
|
||||
if emailAddress != "" {
|
||||
p.inputText = emailAddress
|
||||
@@ -148,7 +139,7 @@ func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error {
|
||||
}
|
||||
// Allow escape back to settings if coming from settings
|
||||
sdl.StopTextInput()
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Context.NavigateTo(SETTINGS)
|
||||
case sdl.K_RETURN:
|
||||
// Validate email - check empty first, then format
|
||||
if p.inputText == "" {
|
||||
@@ -163,16 +154,15 @@ func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error {
|
||||
sdl.StopTextInput()
|
||||
|
||||
// Save email address to database
|
||||
if err := db.SetConfigValue("email_address", p.inputText); err != nil {
|
||||
return fmt.Errorf("failed to save email address: %w", err)
|
||||
}
|
||||
p.Base.Context.CfgPtr.EmailAddress = p.inputText
|
||||
p.Base.Context.DbPtr.Save(p.Base.Context.CfgPtr)
|
||||
p.Base.Context.RefreshConfig()
|
||||
|
||||
if p.isFirstTimeSetup {
|
||||
// First-time setup: navigate to License Key page
|
||||
p.App.PushPage(&LicenseKeyPage{})
|
||||
p.Context.NavigateTo(LICENSE_KEY)
|
||||
} else {
|
||||
// Coming from settings: rewind back to settings
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Context.NavigateTo(SETTINGS)
|
||||
}
|
||||
case sdl.K_BACKSPACE:
|
||||
if len(p.inputText) > 0 {
|
||||
@@ -199,10 +189,7 @@ func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error {
|
||||
// Update updates animations and changing textures and other changes using `dt` which represents a time delta in float32.
|
||||
func (p *EmailAddressPage) Update(dt float32) error {
|
||||
// Get application font from database
|
||||
applicationFont, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFont := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
sm := shaderManager.Get()
|
||||
@@ -266,14 +253,7 @@ func (p *EmailAddressPage) Update(dt float32) error {
|
||||
// Render performs page drawing logic.
|
||||
func (p *EmailAddressPage) Render() error {
|
||||
// Get base font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
sm := shaderManager.Get()
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,14 +11,13 @@ import (
|
||||
"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/shaderManager"
|
||||
)
|
||||
|
||||
var GitVersion = "dev"
|
||||
|
||||
type Welcome struct {
|
||||
type Home struct {
|
||||
Base
|
||||
title StringItem
|
||||
version StringItem
|
||||
@@ -70,7 +68,8 @@ func countSDLDisplays() int {
|
||||
}
|
||||
|
||||
// updateDisplayCounts updates the display count textures
|
||||
func (p *Welcome) updateDisplayCounts() error {
|
||||
func (p *Home) updateDisplayCounts() error {
|
||||
var err error
|
||||
// Get font
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont("Roboto-Regular")
|
||||
@@ -79,14 +78,7 @@ func (p *Welcome) updateDisplayCounts() error {
|
||||
}
|
||||
|
||||
// Get base font size
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Update DRM count
|
||||
p.drmDisplayCount = countConnectedDisplays()
|
||||
@@ -127,14 +119,14 @@ func (p *Welcome) updateDisplayCounts() error {
|
||||
}
|
||||
|
||||
// Init performs basic page initialization and creation of static assets.
|
||||
func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
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(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
@@ -177,19 +169,8 @@ func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("unable to update display counts: %w", err)
|
||||
}
|
||||
|
||||
// Load license information from database
|
||||
emailAddress, err := db.GetConfigValue("email_address")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving email_address from db: %w", err)
|
||||
}
|
||||
|
||||
licenseKey, err := db.GetConfigValue("license_key")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving license_key from db: %w", err)
|
||||
}
|
||||
|
||||
// Create license info textures if data exists
|
||||
if emailAddress != "" || licenseKey != "" {
|
||||
if p.Base.Context.CfgPtr.EmailAddress != "" || p.Base.Context.CfgPtr.LicenseKey != "" {
|
||||
p.hasLicenseInfo = true
|
||||
|
||||
// Create heading
|
||||
@@ -199,8 +180,8 @@ func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Create email display if exists
|
||||
if emailAddress != "" {
|
||||
emailText := fmt.Sprintf("Email: %s", emailAddress)
|
||||
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)
|
||||
@@ -208,8 +189,8 @@ func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Create license key display if exists
|
||||
if licenseKey != "" {
|
||||
licenseKeyText := fmt.Sprintf("License Key: %s", licenseKey)
|
||||
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)
|
||||
@@ -218,14 +199,7 @@ func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Get base font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
p.baseFontSize = baseFontSize
|
||||
|
||||
@@ -249,13 +223,13 @@ func (p *Welcome) Init(app ApplicationInterface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleEvent processes SDL events and handles them appropriately for the Welcome page.
|
||||
func (p *Welcome) HandleEvent(event *sdl.Event) error {
|
||||
// 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 *Welcome) Update(dt float32) error {
|
||||
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 {
|
||||
@@ -265,7 +239,7 @@ func (p *Welcome) Update(dt float32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Welcome) Render() error {
|
||||
func (p *Home) Render() error {
|
||||
baseFontSize := p.baseFontSize
|
||||
sm := shaderManager.Get()
|
||||
verticalMargin := int32(20)
|
||||
@@ -417,7 +391,7 @@ func (p *Welcome) Render() error {
|
||||
}
|
||||
|
||||
// Destroy cleans up page-specific resources.
|
||||
func (p *Welcome) Destroy() error {
|
||||
func (p *Home) Destroy() error {
|
||||
gl.DeleteTextures(1, &p.title.ID)
|
||||
gl.DeleteTextures(1, &p.version.ID)
|
||||
gl.DeleteTextures(1, &p.prompt.ID)
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@@ -47,34 +45,17 @@ type LicenseKeyPage struct {
|
||||
textInputSupported bool
|
||||
}
|
||||
|
||||
// isValidLicenseKey checks if the license key has exactly 16 alphanumeric characters
|
||||
func isValidLicenseKey(key string) bool {
|
||||
cleaned := strings.ReplaceAll(key, "-", "")
|
||||
if len(cleaned) != 16 {
|
||||
return false
|
||||
}
|
||||
for _, r := range cleaned {
|
||||
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Init performs basic page initialization and creation of static assets.
|
||||
func (p *LicenseKeyPage) Init(app ApplicationInterface) error {
|
||||
func (p *LicenseKeyPage) Init(ctx *PageCtx) error {
|
||||
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
p.title = "License Key"
|
||||
|
||||
// Get application font from database
|
||||
applicationFont, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFont := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
textShader, ok := shaderManager.Get().Get("text")
|
||||
@@ -96,10 +77,7 @@ func (p *LicenseKeyPage) Init(app ApplicationInterface) error {
|
||||
p.titleTextureHeight = titleHeight
|
||||
|
||||
// Load existing license key from database
|
||||
licenseKey, err := db.GetConfigValue("license_key")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving license_key from db: %w", err)
|
||||
}
|
||||
licenseKey := p.Base.Context.CfgPtr.LicenseKey
|
||||
|
||||
if licenseKey != "" {
|
||||
p.inputText = licenseKey
|
||||
@@ -187,7 +165,7 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error {
|
||||
}
|
||||
// Allow escape back to settings if coming from settings
|
||||
sdl.StopTextInput()
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Base.Context.NavigateTo(SETTINGS)
|
||||
case sdl.K_RETURN:
|
||||
// Validate license key - check empty first, then length, then characters
|
||||
cleaned := strings.ReplaceAll(p.inputText, "-", "")
|
||||
@@ -210,9 +188,9 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error {
|
||||
sdl.StopTextInput()
|
||||
|
||||
// Save license key to database
|
||||
if err := db.SetConfigValue("license_key", p.inputText); err != nil {
|
||||
return fmt.Errorf("failed to save license key: %w", err)
|
||||
}
|
||||
p.Base.Context.CfgPtr.LicenseKey = p.inputText
|
||||
p.Base.Context.DbPtr.Save(p.Base.Context.CfgPtr)
|
||||
p.Base.Context.RefreshConfig()
|
||||
|
||||
// Write license key to plain text file for the updater
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
@@ -224,10 +202,10 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error {
|
||||
|
||||
if p.isFirstTimeSetup {
|
||||
// First-time setup: rewind to Welcome page
|
||||
p.App.UnwindToPage(&Welcome{})
|
||||
p.Base.Context.NavigateTo(HOME)
|
||||
} else {
|
||||
// Coming from settings: rewind back to settings
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Base.Context.NavigateTo(SETTINGS)
|
||||
}
|
||||
case sdl.K_BACKSPACE:
|
||||
if len(p.inputText) > 0 {
|
||||
@@ -261,10 +239,7 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error {
|
||||
// Update updates animations and changing textures and other changes using `dt` which represents a time delta in float32.
|
||||
func (p *LicenseKeyPage) Update(dt float32) error {
|
||||
// Get application font from database
|
||||
applicationFont, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFont := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
sm := shaderManager.Get()
|
||||
@@ -328,14 +303,7 @@ func (p *LicenseKeyPage) Update(dt float32) error {
|
||||
// Render performs page drawing logic.
|
||||
func (p *LicenseKeyPage) Render() error {
|
||||
// Get base font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
sm := shaderManager.Get()
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -35,27 +36,32 @@ type LogViewerPage struct {
|
||||
visibleHeight int32
|
||||
}
|
||||
|
||||
func (p *LogViewerPage) Init(app ApplicationInterface) error {
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
logs, err := db.GetLogs(1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %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 {
|
||||
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")...)
|
||||
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, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFont := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
textShader, ok := shaderManager.Get().Get("text")
|
||||
@@ -109,7 +115,7 @@ func (p *LogViewerPage) HandleEvent(event *sdl.Event) error {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
switch e.Keysym.Sym {
|
||||
case sdl.K_BACKSPACE:
|
||||
p.App.SchedulePop()
|
||||
p.Base.Context.NavigateTo(SETTINGS)
|
||||
case sdl.K_UP:
|
||||
p.scrollOffset -= p.lineHeight
|
||||
if p.scrollOffset < 0 {
|
||||
@@ -153,14 +159,7 @@ func (p *LogViewerPage) Update(dt float32) error {
|
||||
}
|
||||
|
||||
func (p *LogViewerPage) Render() error {
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
sm := shaderManager.Get()
|
||||
textShader, _ := sm.Get("text")
|
||||
|
||||
+12
-24
@@ -12,29 +12,16 @@ import (
|
||||
|
||||
// Page defines the contract for all application screens.
|
||||
type Page interface {
|
||||
Init(app ApplicationInterface) error
|
||||
Init(ctx *PageCtx) error
|
||||
HandleEvent(event *sdl.Event) error
|
||||
Update(dt float32) error
|
||||
Render() error
|
||||
// Destroy cleans up the page's resources. The Application's page manager
|
||||
// is responsible for calling this method
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
// ApplicationInterface defines the set of methods that a page can use to
|
||||
// interact with the main application.
|
||||
type ApplicationInterface interface {
|
||||
SwitchPage(p Page)
|
||||
PushPage(p Page)
|
||||
UnwindToPage(p Page)
|
||||
GetDrawableSize() (int32, int32)
|
||||
SchedulePop()
|
||||
Quit()
|
||||
}
|
||||
|
||||
// Base is a helper struct that pages can embed to get common functionality.
|
||||
type Base struct {
|
||||
App ApplicationInterface
|
||||
Context *PageCtx
|
||||
QuadVAO uint32
|
||||
QuadVBO uint32
|
||||
Projection mgl32.Mat4
|
||||
@@ -45,9 +32,8 @@ type Base struct {
|
||||
}
|
||||
|
||||
// Init stores the application context and sets up common resources.
|
||||
func (p *Base) Init(app ApplicationInterface) error {
|
||||
p.App = app
|
||||
|
||||
func (p *Base) Init(ctx *PageCtx) error {
|
||||
p.Context = ctx
|
||||
vertices := []float32{
|
||||
// pos(x,y) tex(u,v)
|
||||
0.0, 1.0, 0.0, 1.0,
|
||||
@@ -59,12 +45,14 @@ func (p *Base) Init(app ApplicationInterface) error {
|
||||
1.0, 0.0, 1.0, 0.0,
|
||||
}
|
||||
|
||||
screenWidth, screenHeight := p.App.GetDrawableSize()
|
||||
p.Projection = mgl32.Ortho(0, float32(screenWidth), 0, float32(screenHeight), -1, 1)
|
||||
p.ScreenHeight = screenHeight
|
||||
p.ScreenWidth = screenWidth
|
||||
p.ScreenCenterX = screenWidth / 2
|
||||
p.ScreenCenterY = screenHeight / 2
|
||||
W := p.Context.W
|
||||
H := p.Context.H
|
||||
|
||||
p.Projection = mgl32.Ortho(0, float32(W), 0, float32(H), -1, 1)
|
||||
p.ScreenHeight = H
|
||||
p.ScreenWidth = W
|
||||
p.ScreenCenterY = H / 2
|
||||
p.ScreenCenterX = W / 2
|
||||
|
||||
gl.GenVertexArrays(1, &p.QuadVAO)
|
||||
gl.GenBuffers(1, &p.QuadVBO)
|
||||
|
||||
@@ -2,35 +2,35 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"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 ProclamationOptions struct {
|
||||
Base
|
||||
font *fontManager.FontEntry
|
||||
textShader *shaderManager.Shader
|
||||
solidShader *shaderManager.Shader
|
||||
title StringItem
|
||||
prompt StringItem
|
||||
font *fontManager.FontEntry
|
||||
textShader *shaderManager.Shader
|
||||
solidShader *shaderManager.Shader
|
||||
title StringItem
|
||||
prompt StringItem
|
||||
proclamations []StringItem
|
||||
proclamationIDs []uint // Database IDs corresponding to each proclamation
|
||||
activeMap map[int]bool // Maps display index to selected state
|
||||
highlightIndex int
|
||||
verticalMargin int32
|
||||
proclamationIDs []string
|
||||
activeMap map[int]bool // Maps display index to selected state
|
||||
highlightIndex int
|
||||
verticalMargin int32
|
||||
}
|
||||
|
||||
func (p *ProclamationOptions) Init(app ApplicationInterface) error {
|
||||
func (p *ProclamationOptions) Init(ctx *PageCtx) error {
|
||||
var err error
|
||||
p.verticalMargin = int32(20)
|
||||
p.activeMap = make(map[int]bool)
|
||||
p.proclamationIDs = make([]uint, 0)
|
||||
p.proclamationIDs = make([]string, 0)
|
||||
|
||||
// Get font
|
||||
fm := fontManager.Get()
|
||||
@@ -53,19 +53,12 @@ func (p *ProclamationOptions) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Initialize base
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("base init failed: %w", err)
|
||||
}
|
||||
|
||||
// Get font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size %q: %w", baseFontSizeStr, err)
|
||||
}
|
||||
baseFontSize := ctx.CfgPtr.StandardFontSize
|
||||
|
||||
// Calculate scales
|
||||
titleScale := float32(64) / float32(baseFontSize)
|
||||
@@ -86,9 +79,10 @@ func (p *ProclamationOptions) Init(app ApplicationInterface) error {
|
||||
p.prompt.Scale(itemScale)
|
||||
|
||||
// Load proclamations from database
|
||||
proclamations, err := db.GetProclamations()
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving proclamations from db: %w", err)
|
||||
var proclamations []db.Proclamation
|
||||
if result := p.Context.DbPtr.Find(&proclamations); result.Error != nil {
|
||||
logger.Error("unable to query database for proclamations: %v", result.Error)
|
||||
return fmt.Errorf("%s", result.Error)
|
||||
}
|
||||
|
||||
// Create UI items for each available proclamation
|
||||
@@ -97,7 +91,7 @@ func (p *ProclamationOptions) Init(app ApplicationInterface) error {
|
||||
continue
|
||||
}
|
||||
|
||||
label, err := NewStringItem(proclamation.Title, p.font, colors.White)
|
||||
label, err := NewStringItem(proclamation.Name, p.font, colors.White)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating proclamation label: %w", err)
|
||||
}
|
||||
@@ -152,7 +146,7 @@ func (p *ProclamationOptions) HandleEvent(event *sdl.Event) error {
|
||||
p.highlightIndex++
|
||||
}
|
||||
case sdl.K_BACKSPACE:
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Context.NavigateTo(SETTINGS)
|
||||
case sdl.K_SPACE:
|
||||
// Toggle selection for highlighted item
|
||||
if p.activeMap[p.highlightIndex] {
|
||||
@@ -167,7 +161,7 @@ func (p *ProclamationOptions) HandleEvent(event *sdl.Event) error {
|
||||
}
|
||||
case sdl.K_RETURN:
|
||||
// Just return to settings - no saving needed
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Context.NavigateTo(SETTINGS)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -183,15 +177,17 @@ func (p *ProclamationOptions) toggleProclamation(displayIndex int) error {
|
||||
isSelected := p.activeMap[displayIndex]
|
||||
|
||||
// Get the proclamation from database
|
||||
var proclamation db.Proclamations
|
||||
if err := db.DB.First(&proclamation, dbID).Error; err != nil {
|
||||
return fmt.Errorf("retrieving proclamation %d: %w", dbID, err)
|
||||
var proclamation db.Proclamation
|
||||
proclamation.ID = dbID
|
||||
if result := p.Context.DbPtr.Find(&proclamation); result.Error != nil {
|
||||
logger.Error("unable to query proclamation %s from database: %v", proclamation.ID, result.Error)
|
||||
}
|
||||
|
||||
// Update the Selected field
|
||||
proclamation.Selected = isSelected
|
||||
if err := db.DB.Save(&proclamation).Error; err != nil {
|
||||
return fmt.Errorf("updating proclamation %d: %w", dbID, err)
|
||||
if result := p.Context.DbPtr.Save(&proclamation); result.Error != nil {
|
||||
logger.Error("failed to update proclamation %s: %v", proclamation.ID, result.Error)
|
||||
return fmt.Errorf("%s", result.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"rhemawavetech.com/rhemawave/internal/db"
|
||||
)
|
||||
|
||||
type pageType int
|
||||
|
||||
const (
|
||||
HOME pageType = iota
|
||||
SCROLLER
|
||||
SETTINGS
|
||||
WIFI
|
||||
AUDIO
|
||||
PROCLAMATIONS
|
||||
LICENSE_KEY
|
||||
EMAIL_ADDRESS
|
||||
LOGS
|
||||
CHECK_UPDATES
|
||||
ERROR
|
||||
)
|
||||
|
||||
type PageCtx struct {
|
||||
CfgPtr *db.AppConfig
|
||||
DbPtr *gorm.DB
|
||||
W, H int32
|
||||
RefreshConfig func()
|
||||
NavigateTo func(pageType)
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
ActivePage Page
|
||||
Context PageCtx
|
||||
}
|
||||
|
||||
func NewRouter(pgCtx PageCtx) *Router {
|
||||
r := &Router{
|
||||
Context: pgCtx,
|
||||
}
|
||||
r.Context.NavigateTo = r.NavigateTo
|
||||
r.NavigateTo(HOME)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Router) NavigateTo(p pageType) {
|
||||
if r.ActivePage != nil {
|
||||
r.ActivePage.Destroy()
|
||||
}
|
||||
|
||||
switch p {
|
||||
case HOME:
|
||||
r.ActivePage = &Home{}
|
||||
case SCROLLER:
|
||||
r.ActivePage = &ScrollerPage{}
|
||||
case SETTINGS:
|
||||
r.ActivePage = &Settings{}
|
||||
case WIFI:
|
||||
r.ActivePage = &WiFiSetupPage{}
|
||||
case AUDIO:
|
||||
r.ActivePage = &AudioDevices{}
|
||||
case PROCLAMATIONS:
|
||||
r.ActivePage = &ProclamationOptions{}
|
||||
case LICENSE_KEY:
|
||||
r.ActivePage = &LicenseKeyPage{}
|
||||
case EMAIL_ADDRESS:
|
||||
r.ActivePage = &EmailAddressPage{}
|
||||
case LOGS:
|
||||
r.ActivePage = &LogViewerPage{}
|
||||
case CHECK_UPDATES:
|
||||
r.ActivePage = &UpdateCheckPage{}
|
||||
}
|
||||
|
||||
r.ActivePage.Init(&r.Context)
|
||||
}
|
||||
@@ -60,7 +60,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
@@ -194,7 +193,7 @@ type ScrollerPage struct {
|
||||
hold float32
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
func (p *ScrollerPage) Init(ctx *PageCtx) error {
|
||||
p.increment = 100.0
|
||||
p.velocity = 800.0
|
||||
p.SetAutoVelocity(true, -4000.0, 4000.0, 100.0, 2.5, 6.0)
|
||||
@@ -209,7 +208,7 @@ func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
}
|
||||
p.font = font
|
||||
|
||||
p.Base.Init(app)
|
||||
p.Base.Init(ctx)
|
||||
|
||||
// Get text shader
|
||||
textShader, ok := sm.Get("text")
|
||||
@@ -226,10 +225,8 @@ func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
p.wordIndex = 0
|
||||
|
||||
// Get line pattern from database
|
||||
linePatternName, err := db.GetConfigValue("line_pattern")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving line_pattern from db: %w", err)
|
||||
}
|
||||
var err error
|
||||
linePatternName := p.Base.Context.CfgPtr.LinePattern
|
||||
|
||||
p.activePattern, err = linePattern.Get(linePatternName)
|
||||
if err != nil {
|
||||
@@ -249,23 +246,8 @@ func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
p.colorPattern = colorPattern.NewColorPattern(baseColors)
|
||||
|
||||
// Get font sizes from database (optimize: load once, cache as int32)
|
||||
standardFontSizeStr, err := db.GetConfigValue("standard_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving standard_font_size from db: %w", err)
|
||||
}
|
||||
standardFontSize, err := strconv.Atoi(standardFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing standard_font_size: %w", err)
|
||||
}
|
||||
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
standardFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
baseFontSize := p.Base.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Font setup (optimized: calculate once, reuse)
|
||||
p.fontScale = float32(standardFontSize) / float32(baseFontSize)
|
||||
@@ -302,10 +284,7 @@ func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Audio setup
|
||||
audioDeviceName, err := db.GetConfigValue("audio_device_name")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving audio_device_name from db: %w", err)
|
||||
}
|
||||
audioDeviceName := p.Base.Context.CfgPtr.SelectedAudioDevice
|
||||
|
||||
// Switch audio devices if needed
|
||||
if mixer.CurrentDevice() != audioDeviceName {
|
||||
@@ -315,19 +294,13 @@ func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Start audio playback
|
||||
selectedAudio, err := db.GetConfigValue("selected_audio")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving selected_audio from db: %w", err)
|
||||
}
|
||||
selectedAudio := p.Base.Context.CfgPtr.SelectedAudio
|
||||
|
||||
assetsDir, err := db.GetConfigValue("assets_dir")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving assets_dir from db: %w", err)
|
||||
}
|
||||
assetsDir := p.Base.Context.CfgPtr.AssetsDir
|
||||
|
||||
if selectedAudio == "" {
|
||||
} else {
|
||||
if err := mixer.Play(filepath.Join(assetsDir, selectedAudio), true); err != nil {
|
||||
if err := mixer.Play(filepath.Join(assetsDir, selectedAudio), true, p.Base.Context.CfgPtr); err != nil {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,10 +317,8 @@ func (p *ScrollerPage) loadProclamations() error {
|
||||
p.words = []string{}
|
||||
|
||||
// Get all selected proclamations from database
|
||||
proclamations, err := db.GetProclamations()
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving proclamations from db: %w", err)
|
||||
}
|
||||
var proclamations []db.Proclamation
|
||||
p.Base.Context.DbPtr.Find(&proclamations)
|
||||
|
||||
// Build word list from selected, available proclamations
|
||||
for _, proclamation := range proclamations {
|
||||
@@ -614,7 +585,7 @@ func (p *ScrollerPage) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
mixer.Stop(1000)
|
||||
mixer.Stop(1000, p.Context.CfgPtr)
|
||||
|
||||
// GL cleanup — delete all text textures
|
||||
for i := 0; i < p.lines.Len(); i++ {
|
||||
|
||||
@@ -2,12 +2,10 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"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/shaderManager"
|
||||
)
|
||||
@@ -26,11 +24,11 @@ type Settings struct {
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
page Page
|
||||
label StringItem
|
||||
target pageType
|
||||
label StringItem
|
||||
}
|
||||
|
||||
func (p *Settings) Init(app ApplicationInterface) error {
|
||||
func (p *Settings) Init(ctx *PageCtx) error {
|
||||
var err error
|
||||
p.verticalMargin = int32(20)
|
||||
|
||||
@@ -54,7 +52,7 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Initialize base
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
@@ -76,8 +74,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating proclamations label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &ProclamationOptions{},
|
||||
label: proclamationsLabel,
|
||||
target: PROCLAMATIONS,
|
||||
label: proclamationsLabel,
|
||||
})
|
||||
|
||||
wifiLabel, err := NewStringItem("WiFi Setup", font, colors.White)
|
||||
@@ -85,8 +83,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating wifi label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &WiFiSetupPage{},
|
||||
label: wifiLabel,
|
||||
target: WIFI,
|
||||
label: wifiLabel,
|
||||
})
|
||||
|
||||
audioLabel, err := NewStringItem("Audio Device", font, colors.White)
|
||||
@@ -94,8 +92,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating audio label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &AudioDevices{},
|
||||
label: audioLabel,
|
||||
target: AUDIO,
|
||||
label: audioLabel,
|
||||
})
|
||||
|
||||
emailLabel, err := NewStringItem("Email Address", font, colors.White)
|
||||
@@ -103,8 +101,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating email label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &EmailAddressPage{},
|
||||
label: emailLabel,
|
||||
target: EMAIL_ADDRESS,
|
||||
label: emailLabel,
|
||||
})
|
||||
|
||||
licenseKeyLabel, err := NewStringItem("License Key", font, colors.White)
|
||||
@@ -112,8 +110,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating license key label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &LicenseKeyPage{},
|
||||
label: licenseKeyLabel,
|
||||
target: LICENSE_KEY,
|
||||
label: licenseKeyLabel,
|
||||
})
|
||||
|
||||
logsLabel, err := NewStringItem("Logs", font, colors.White)
|
||||
@@ -121,8 +119,8 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating logs label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &LogViewerPage{},
|
||||
label: logsLabel,
|
||||
target: LOGS,
|
||||
label: logsLabel,
|
||||
})
|
||||
|
||||
updateLabel, err := NewStringItem("Check for Updates", font, colors.White)
|
||||
@@ -130,19 +128,12 @@ func (p *Settings) Init(app ApplicationInterface) error {
|
||||
return fmt.Errorf("creating update label: %w", err)
|
||||
}
|
||||
p.options = append(p.options, Option{
|
||||
page: &UpdateCheckPage{},
|
||||
label: updateLabel,
|
||||
target: CHECK_UPDATES,
|
||||
label: updateLabel,
|
||||
})
|
||||
|
||||
// Get base font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Calculate scales
|
||||
titleScale := float32(64) / float32(baseFontSize)
|
||||
@@ -208,10 +199,10 @@ func (p *Settings) HandleEvent(event *sdl.Event) error {
|
||||
case sdl.K_RETURN:
|
||||
if p.highlightIndex >= 0 && p.highlightIndex < len(p.options) {
|
||||
selectedOption := p.options[p.highlightIndex]
|
||||
p.App.SwitchPage(selectedOption.page)
|
||||
p.Context.NavigateTo(selectedOption.target)
|
||||
}
|
||||
case sdl.K_BACKSPACE:
|
||||
p.App.SwitchPage(&Welcome{})
|
||||
p.Context.NavigateTo(HOME)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,388 +0,0 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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 TOSPage struct {
|
||||
Base
|
||||
|
||||
Title string
|
||||
Content string
|
||||
ConfigKey string
|
||||
|
||||
titleTextureID uint32
|
||||
titleTextureWidth int32
|
||||
titleTextureHeight int32
|
||||
|
||||
lines []string
|
||||
lineTextureIDs []uint32
|
||||
lineTextureWidths []int32
|
||||
lineTextureHeights []int32
|
||||
|
||||
promptTextureID uint32
|
||||
promptTextureWidth int32
|
||||
promptTextureHeight int32
|
||||
|
||||
scrollHintTextureID uint32
|
||||
scrollHintTextureWidth int32
|
||||
scrollHintTextureHeight int32
|
||||
|
||||
scrollOffset int32
|
||||
maxScrollOffset int32
|
||||
lineHeight int32
|
||||
visibleHeight int32
|
||||
}
|
||||
|
||||
func (p *TOSPage) Init(app ApplicationInterface) error {
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
p.lines = parseHTMLToLines(p.Content, 60)
|
||||
|
||||
applicationFont, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
|
||||
fm := fontManager.Get()
|
||||
textShader, ok := shaderManager.Get().Get("text")
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to fetch text shader")
|
||||
}
|
||||
|
||||
titleTextureID, titleWidth, titleHeight, err := fm.CreateStringTexture(applicationFont, p.Title, textShader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create title texture: %w", err)
|
||||
}
|
||||
p.titleTextureID = titleTextureID
|
||||
p.titleTextureWidth = titleWidth
|
||||
p.titleTextureHeight = titleHeight
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
promptText := "Press Y to Accept | Press N to Decline"
|
||||
promptTextureID, promptWidth, promptHeight, err := fm.CreateStringTexture(applicationFont, promptText, textShader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create prompt texture: %w", err)
|
||||
}
|
||||
p.promptTextureID = promptTextureID
|
||||
p.promptTextureWidth = promptWidth
|
||||
p.promptTextureHeight = promptHeight
|
||||
|
||||
scrollHintText := "Scroll down: Arrow Keys / PgDn"
|
||||
scrollHintID, scrollHintW, scrollHintH, err := fm.CreateStringTexture(applicationFont, scrollHintText, textShader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create scroll hint texture: %w", err)
|
||||
}
|
||||
p.scrollHintTextureID = scrollHintID
|
||||
p.scrollHintTextureWidth = scrollHintW
|
||||
p.scrollHintTextureHeight = scrollHintH
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseHTMLToLines converts HTML content to plain-text lines for rendering.
|
||||
// Supported tags: <p>, <br>, <li>, <h1>-<h6>. All other tags are stripped.
|
||||
// HTML entities & < > " ' are decoded.
|
||||
func parseHTMLToLines(html string, maxCharsPerLine int) []string {
|
||||
var lines []string
|
||||
|
||||
// Replace <br>, <br/>, <br /> with newline markers
|
||||
br := strings.NewReplacer(
|
||||
"<br>", "\n", "<br/>", "\n", "<br />", "\n",
|
||||
"<BR>", "\n", "<BR/>", "\n", "<BR />", "\n",
|
||||
)
|
||||
html = br.Replace(html)
|
||||
|
||||
// Process block-level tags
|
||||
// Replace </p>, </h1>-</h6> with paragraph break marker
|
||||
for _, tag := range []string{"</p>", "</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>"} {
|
||||
html = strings.ReplaceAll(html, tag, "\n\n")
|
||||
html = strings.ReplaceAll(html, strings.ToUpper(tag), "\n\n")
|
||||
}
|
||||
|
||||
// Replace <li> with list prefix marker
|
||||
html = strings.ReplaceAll(html, "<li>", "\n- ")
|
||||
html = strings.ReplaceAll(html, "<LI>", "\n- ")
|
||||
|
||||
// Strip all remaining HTML tags
|
||||
var stripped strings.Builder
|
||||
inTag := false
|
||||
for _, r := range html {
|
||||
if r == '<' {
|
||||
inTag = true
|
||||
continue
|
||||
}
|
||||
if r == '>' {
|
||||
inTag = false
|
||||
continue
|
||||
}
|
||||
if !inTag {
|
||||
stripped.WriteRune(r)
|
||||
}
|
||||
}
|
||||
text := stripped.String()
|
||||
|
||||
// Decode HTML entities
|
||||
text = strings.ReplaceAll(text, "&", "&")
|
||||
text = strings.ReplaceAll(text, "<", "<")
|
||||
text = strings.ReplaceAll(text, ">", ">")
|
||||
text = strings.ReplaceAll(text, """, "\"")
|
||||
text = strings.ReplaceAll(text, "'", "'")
|
||||
|
||||
// Split into segments on double-newline (paragraph breaks) and single newline (line breaks)
|
||||
paragraphs := strings.Split(text, "\n\n")
|
||||
for i, para := range paragraphs {
|
||||
para = strings.TrimSpace(para)
|
||||
if para == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle single newlines within a paragraph (from <br> tags)
|
||||
sublines := strings.Split(para, "\n")
|
||||
for _, sub := range sublines {
|
||||
sub = strings.TrimSpace(sub)
|
||||
if sub == "" {
|
||||
continue
|
||||
}
|
||||
wrapped := wrapText(sub, maxCharsPerLine)
|
||||
lines = append(lines, strings.Split(wrapped, "\n")...)
|
||||
}
|
||||
|
||||
// Add paragraph break (empty line) between paragraphs
|
||||
if i < len(paragraphs)-1 {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func wrapText(text string, maxCharsPerLine int) string {
|
||||
var result strings.Builder
|
||||
words := strings.Fields(text)
|
||||
lineLen := 0
|
||||
|
||||
for _, word := range words {
|
||||
if lineLen+len(word)+1 > maxCharsPerLine && lineLen > 0 {
|
||||
result.WriteString("\n")
|
||||
lineLen = 0
|
||||
}
|
||||
if lineLen > 0 {
|
||||
result.WriteString(" ")
|
||||
lineLen++
|
||||
}
|
||||
result.WriteString(word)
|
||||
lineLen += len(word)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (p *TOSPage) HandleEvent(event *sdl.Event) error {
|
||||
actualEvent := *event
|
||||
|
||||
switch e := actualEvent.(type) {
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
switch e.Keysym.Sym {
|
||||
case sdl.K_n:
|
||||
logger.Info("User declined TOS (%s); exiting", p.ConfigKey)
|
||||
p.App.Quit()
|
||||
case sdl.K_y:
|
||||
if p.scrollOffset < p.maxScrollOffset {
|
||||
return nil
|
||||
}
|
||||
logger.Info("User accepted TOS (%s)", p.ConfigKey)
|
||||
if err := db.SetConfigValue(p.ConfigKey, "true"); err != nil {
|
||||
return fmt.Errorf("failed to save TOS acceptance (%s): %w", p.ConfigKey, err)
|
||||
}
|
||||
p.App.SchedulePop()
|
||||
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 *TOSPage) Update(dt float32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TOSPage) Render() error {
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
|
||||
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(32)
|
||||
contentScale := float32(contentFontSize) / float32(baseFontSize)
|
||||
promptFontSize := int32(20)
|
||||
promptScale := float32(promptFontSize) / float32(baseFontSize)
|
||||
|
||||
margin := int32(40)
|
||||
lineSpacing := int32(4)
|
||||
paragraphSpacing := int32(16)
|
||||
p.lineHeight = contentFontSize + lineSpacing
|
||||
|
||||
// Center a content column at ~55% of screen width, left-align text within it
|
||||
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)
|
||||
|
||||
// Prompt dimensions (calculate even if not shown, for content bounds)
|
||||
promptH := float32(p.promptTextureHeight) * promptScale
|
||||
promptW := float32(p.promptTextureWidth) * promptScale
|
||||
promptX := p.Base.ScreenCenterX - int32(promptW/2)
|
||||
promptY := margin
|
||||
|
||||
// Content area bounds
|
||||
contentTop := titleY - margin
|
||||
contentBottom := promptY + int32(promptH) + margin
|
||||
p.visibleHeight = contentTop - contentBottom
|
||||
|
||||
// Calculate total content height (accounting for paragraph breaks)
|
||||
totalContentHeight := int32(0)
|
||||
for _, line := range p.lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
totalContentHeight += paragraphSpacing
|
||||
} else {
|
||||
totalContentHeight += 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 -= paragraphSpacing
|
||||
continue
|
||||
}
|
||||
|
||||
if p.lineTextureIDs[i] == 0 {
|
||||
currentY -= p.lineHeight
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip lines outside visible area
|
||||
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
|
||||
}
|
||||
|
||||
// Show accept/decline prompt when scrolled to bottom, scroll hint otherwise
|
||||
if p.scrollOffset >= p.maxScrollOffset {
|
||||
p.Base.RenderTexture(textShader, p.promptTextureID, mgl32.Vec2{float32(promptX), float32(promptY)}, mgl32.Vec2{promptW, promptH}, grey)
|
||||
} else {
|
||||
hintScale := promptScale
|
||||
hintW := float32(p.scrollHintTextureWidth) * hintScale
|
||||
hintH := float32(p.scrollHintTextureHeight) * hintScale
|
||||
hintX := p.Base.ScreenCenterX - int32(hintW/2)
|
||||
hintY := promptY
|
||||
p.Base.RenderTexture(textShader, p.scrollHintTextureID, mgl32.Vec2{float32(hintX), float32(hintY)}, mgl32.Vec2{hintW, hintH}, grey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TOSPage) Destroy() error {
|
||||
gl.DeleteTextures(1, &p.titleTextureID)
|
||||
gl.DeleteTextures(1, &p.promptTextureID)
|
||||
gl.DeleteTextures(1, &p.scrollHintTextureID)
|
||||
|
||||
for _, texID := range p.lineTextureIDs {
|
||||
if texID != 0 {
|
||||
gl.DeleteTextures(1, &texID)
|
||||
}
|
||||
}
|
||||
|
||||
return p.Base.Destroy()
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package page
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"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/shaderManager"
|
||||
)
|
||||
@@ -43,8 +41,9 @@ type UpdateCheckPage struct {
|
||||
dotTimer float32
|
||||
}
|
||||
|
||||
func (p *UpdateCheckPage) Init(app ApplicationInterface) error {
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
func (p *UpdateCheckPage) Init(ctx *PageCtx) error {
|
||||
var err error
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
@@ -62,14 +61,7 @@ func (p *UpdateCheckPage) Init(app ApplicationInterface) error {
|
||||
}
|
||||
|
||||
// Get scale
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := ctx.CfgPtr.InitialFontSize
|
||||
scale := float32(48) / float32(baseFontSize)
|
||||
|
||||
// Create checking textures with animated dots
|
||||
@@ -149,7 +141,7 @@ func (p *UpdateCheckPage) HandleEvent(event *sdl.Event) error {
|
||||
}
|
||||
switch e.Keysym.Sym {
|
||||
case sdl.K_F3, sdl.K_BACKSPACE:
|
||||
p.App.UnwindToPage(&Settings{})
|
||||
p.Context.NavigateTo(SETTINGS)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,7 +2,6 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/godbus/dbus/v5"
|
||||
"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/keybinds"
|
||||
"rhemawavetech.com/rhemawave/internal/logger"
|
||||
@@ -69,17 +67,14 @@ type WiFiSetupPage struct {
|
||||
}
|
||||
|
||||
// Init sets up the page, initializes the network manager, and starts a scan.
|
||||
func (p *WiFiSetupPage) Init(app ApplicationInterface) error {
|
||||
func (p *WiFiSetupPage) Init(ctx *PageCtx) error {
|
||||
|
||||
if err := p.Base.Init(app); err != nil {
|
||||
if err := p.Base.Init(ctx); err != nil {
|
||||
return fmt.Errorf("failed base page initialization: %w", err)
|
||||
}
|
||||
|
||||
// Get application font from database
|
||||
applicationFontName, err := db.GetConfigValue("application_font")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving application_font from db: %w", err)
|
||||
}
|
||||
applicationFontName := "Roboto-Regular"
|
||||
|
||||
fm := fontManager.Get()
|
||||
font, ok := fm.GetFont(applicationFontName)
|
||||
@@ -629,14 +624,7 @@ func (p *WiFiSetupPage) updatePasswordErrorTexture() {
|
||||
// Render draws the entire page.
|
||||
func (p *WiFiSetupPage) Render() error {
|
||||
// Get base font size from database
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.ParseFloat(baseFontSizeStr, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
baseFontSize := p.Context.CfgPtr.StandardFontSize
|
||||
|
||||
// Colors & Config
|
||||
bgColor := sdl.Color{R: 0, G: 0, B: 0, A: 255}
|
||||
|
||||
Reference in New Issue
Block a user