Replace in-memory atomic AppConfig caching with direct DB queries via db.GetConfig across application and page layers. This simplifies state management and ensures all components always operate on the latest persisted configuration. Key changes: - Remove Application.Config atomic cache and refreshCache mechanism - Eliminate PageCtx.CfgPtr and pass DB handle instead - Replace all cfg reads with db.GetConfig(app.DB) - Update mixer, audio, UI pages, and initialization paths accordingly - Explicitly set AppConfig table name to avoid GORM pluralization issues - Reduce DB connection pooling (SetMaxIdleConns(1)) - Standardize application font to SpaceMonoNerdFont-Regular - Lower default audio volume (1.0 → 0.2) - Add Unicode checkmark glyph to font map Behavioral impact: - Configuration is no longer cached in memory - Every config access now hits the DB (more consistent, slightly more overhead)
661 lines
17 KiB
Go
661 lines
17 KiB
Go
package page
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"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/logger"
|
|
"rhemawavetech.com/rhemawave/internal/network"
|
|
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
|
)
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
type networkUIItem struct {
|
|
Net network.Network
|
|
Item StringItem
|
|
}
|
|
|
|
type wifiStatus int
|
|
|
|
const (
|
|
wifiStatusIdle wifiStatus = iota
|
|
wifiStatusScanning
|
|
wifiStatusConnecting
|
|
wifiStatusConnected
|
|
wifiStatusError
|
|
)
|
|
|
|
// ─── Page ─────────────────────────────────────────────────────────────────────
|
|
|
|
type WiFiSetupPage struct {
|
|
Base
|
|
applicationFont *fontManager.FontEntry
|
|
|
|
// UI elements
|
|
BaseFontSize int
|
|
titleItem StringItem
|
|
scanningStatusItem StringItem
|
|
connectionStatusItem StringItem
|
|
passwordPromptItem StringItem
|
|
passwordInputItem StringItem
|
|
passwordErrorItem StringItem
|
|
|
|
// Network list
|
|
networks []networkUIItem
|
|
selectedIndex int
|
|
|
|
// State
|
|
status wifiStatus
|
|
statusMessage string
|
|
connectError string
|
|
|
|
// Password entry
|
|
isEnteringPassword bool
|
|
passwordInput string
|
|
selectedNet *network.Network
|
|
|
|
// Async
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
networksCh <-chan []network.Network
|
|
connectResultCh chan error
|
|
}
|
|
|
|
// ─── Init ─────────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) Init(ctx *PageCtx) error {
|
|
if err := p.Base.Init(ctx); err != nil {
|
|
return fmt.Errorf("failed base page initialization: %w", err)
|
|
}
|
|
|
|
fm := fontManager.Get()
|
|
font, ok := fm.GetFont("SpaceMonoNerdFont-Regular")
|
|
if !ok {
|
|
return fmt.Errorf("could not find application font: SpaceMonoNerdFont-Regular")
|
|
}
|
|
p.applicationFont = font
|
|
|
|
var err error
|
|
|
|
p.titleItem, err = NewStringItem("Wi-Fi Setup", p.applicationFont, colors.White)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create title item: %w", err)
|
|
}
|
|
p.scanningStatusItem, err = NewStringItem("Scanning for networks...", p.applicationFont, colors.White)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create scanning status item: %w", err)
|
|
}
|
|
|
|
config, err := db.GetConfig(p.Context.DbPtr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
p.BaseFontSize = config.StandardFontSize
|
|
|
|
p.connectResultCh = make(chan error, 1)
|
|
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
|
|
p.startScan()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ─── Scan ─────────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) startScan() {
|
|
p.status = wifiStatusScanning
|
|
p.statusMessage = "Scanning for networks..."
|
|
p.selectedIndex = 0
|
|
p.networks = nil
|
|
|
|
var err error
|
|
p.networksCh, err = network.ScanForNetworks(p.ctx)
|
|
if err != nil {
|
|
logger.Error("wifi: failed to start scan: %v", err)
|
|
p.status = wifiStatusError
|
|
p.statusMessage = "Failed to start network scan. No Wi-Fi adapter found."
|
|
}
|
|
}
|
|
|
|
// ─── Input ────────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error {
|
|
switch e := (*event).(type) {
|
|
|
|
case *sdl.TextInputEvent:
|
|
// SDL fires TextInputEvent for every printable character typed.
|
|
// This is the correct SDL2 way — no manual keycode mapping needed.
|
|
if p.isEnteringPassword {
|
|
p.passwordInput += e.GetText()
|
|
p.rebuildPasswordTexture()
|
|
}
|
|
|
|
case *sdl.KeyboardEvent:
|
|
if e.Type != sdl.KEYDOWN {
|
|
return nil
|
|
}
|
|
if p.isEnteringPassword {
|
|
p.handlePasswordKey(e.Keysym.Sym)
|
|
} else {
|
|
p.handleNavKey(e.Keysym.Sym)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *WiFiSetupPage) handlePasswordKey(sym sdl.Keycode) {
|
|
switch sym {
|
|
case sdl.K_RETURN:
|
|
p.submitPassword()
|
|
|
|
case sdl.K_BACKSPACE:
|
|
if len(p.passwordInput) > 0 {
|
|
// Trim one rune safely
|
|
runes := []rune(p.passwordInput)
|
|
p.passwordInput = string(runes[:len(runes)-1])
|
|
p.rebuildPasswordTexture()
|
|
}
|
|
|
|
case sdl.K_ESCAPE:
|
|
logger.Info("wifi: password entry cancelled")
|
|
p.exitPasswordMode()
|
|
p.connectError = ""
|
|
p.selectedNet = nil
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) handleNavKey(sym sdl.Keycode) {
|
|
switch sym {
|
|
case sdl.K_UP:
|
|
if p.selectedIndex > 0 {
|
|
p.selectedIndex--
|
|
}
|
|
|
|
case sdl.K_DOWN:
|
|
if p.selectedIndex < len(p.networks)-1 {
|
|
p.selectedIndex++
|
|
}
|
|
|
|
case sdl.K_RETURN:
|
|
if len(p.networks) == 0 || p.selectedIndex >= len(p.networks) {
|
|
return
|
|
}
|
|
selected := p.networks[p.selectedIndex].Net
|
|
p.selectedNet = &selected
|
|
p.connectError = ""
|
|
|
|
if selected.Security != network.SecurityOpen {
|
|
p.passwordInput = ""
|
|
p.enterPasswordMode()
|
|
} else {
|
|
p.connect("")
|
|
}
|
|
|
|
case sdl.K_r:
|
|
if p.status != wifiStatusScanning && p.status != wifiStatusConnecting {
|
|
p.startScan()
|
|
}
|
|
|
|
case sdl.K_ESCAPE:
|
|
p.selectedNet = nil
|
|
p.connectError = ""
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) enterPasswordMode() {
|
|
keybinds.Enabled = false
|
|
p.isEnteringPassword = true
|
|
p.rebuildPasswordPromptTexture()
|
|
p.rebuildPasswordTexture()
|
|
sdl.StartTextInput()
|
|
logger.Info("wifi: password entry started for %q", p.selectedNet.SSID)
|
|
}
|
|
|
|
func (p *WiFiSetupPage) exitPasswordMode() {
|
|
keybinds.Enabled = true
|
|
p.isEnteringPassword = false
|
|
sdl.StopTextInput()
|
|
logger.Info("wifi: password entry finished")
|
|
}
|
|
|
|
func (p *WiFiSetupPage) submitPassword() {
|
|
p.exitPasswordMode()
|
|
p.connect(p.passwordInput)
|
|
}
|
|
|
|
// ─── Connect ──────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) connect(password string) {
|
|
if p.selectedNet == nil {
|
|
return
|
|
}
|
|
|
|
ssid := p.selectedNet.SSID
|
|
p.status = wifiStatusConnecting
|
|
p.statusMessage = fmt.Sprintf("Connecting to \"%s\"...", ssid)
|
|
logger.Info("wifi: connecting to %q", ssid)
|
|
|
|
// ConnectToNetwork blocks (up to 15s), so run it off the main goroutine.
|
|
go func() {
|
|
err := network.ConnectToNetwork(ssid, password)
|
|
p.connectResultCh <- err
|
|
}()
|
|
}
|
|
|
|
// ─── Update ───────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) Update(dt float32) error {
|
|
select {
|
|
|
|
case nets, ok := <-p.networksCh:
|
|
if ok {
|
|
p.rebuildNetworkList(nets)
|
|
if p.status == wifiStatusScanning {
|
|
p.status = wifiStatusIdle
|
|
p.statusMessage = ""
|
|
}
|
|
}
|
|
|
|
case err := <-p.connectResultCh:
|
|
if err != nil {
|
|
logger.Error("wifi: connection failed: %v", err)
|
|
p.status = wifiStatusError
|
|
p.connectError = fmt.Sprintf("Could not connect: %s", err.Error())
|
|
p.statusMessage = ""
|
|
|
|
// Drop back into password entry so the user can try again.
|
|
if p.selectedNet != nil && p.selectedNet.Security != network.SecurityOpen {
|
|
p.passwordInput = ""
|
|
p.rebuildPasswordErrorTexture()
|
|
p.enterPasswordMode()
|
|
}
|
|
} else {
|
|
ssid := ""
|
|
if p.selectedNet != nil {
|
|
ssid = p.selectedNet.SSID
|
|
}
|
|
logger.Info("wifi: connected to %q", ssid)
|
|
p.status = wifiStatusConnected
|
|
p.connectError = ""
|
|
p.statusMessage = fmt.Sprintf("Connected to \"%s\" — press F1 to continue", ssid)
|
|
// Trigger a fresh scan so the list shows the connected state.
|
|
p.startScan()
|
|
}
|
|
|
|
default:
|
|
}
|
|
|
|
p.rebuildStatusTexture()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ─── Texture builders ─────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) rebuildNetworkList(nets []network.Network) {
|
|
for _, item := range p.networks {
|
|
if item.Item.ID != 0 {
|
|
gl.DeleteTextures(1, &item.Item.ID)
|
|
}
|
|
}
|
|
|
|
p.networks = make([]networkUIItem, 0, len(nets))
|
|
|
|
for _, n := range nets {
|
|
label := n.SSID
|
|
switch {
|
|
case n.Connected:
|
|
label += " ✓"
|
|
case n.Saved:
|
|
label += " ★"
|
|
}
|
|
if n.Security != network.SecurityOpen {
|
|
var secStr string
|
|
switch n.Security {
|
|
case network.SecurityWEP:
|
|
secStr = "WEP"
|
|
case network.SecurityWPA:
|
|
secStr = "WPA"
|
|
case network.SecurityWPA2:
|
|
secStr = "WPA2"
|
|
case network.SecurityWPA3:
|
|
secStr = "WPA3"
|
|
default:
|
|
secStr = "Secured"
|
|
}
|
|
label += " [" + secStr + "]"
|
|
}
|
|
|
|
c := colors.White
|
|
if n.Connected {
|
|
c = colors.LawnGreen
|
|
}
|
|
|
|
item, err := NewStringItem(label, p.applicationFont, c)
|
|
if err != nil {
|
|
logger.Warning("wifi: failed to create item for %q: %v", n.SSID, err)
|
|
continue
|
|
}
|
|
p.networks = append(p.networks, networkUIItem{Net: n, Item: item})
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) rebuildStatusTexture() {
|
|
if p.connectionStatusItem.ID != 0 {
|
|
gl.DeleteTextures(1, &p.connectionStatusItem.ID)
|
|
p.connectionStatusItem.ID = 0
|
|
}
|
|
|
|
var msg string
|
|
var c sdl.Color
|
|
|
|
switch p.status {
|
|
case wifiStatusScanning:
|
|
msg = "Scanning for nearby networks, please wait..."
|
|
c = colors.White
|
|
case wifiStatusConnecting:
|
|
msg = p.statusMessage
|
|
c = colors.Yellow
|
|
case wifiStatusConnected:
|
|
msg = p.statusMessage
|
|
c = colors.LawnGreen
|
|
case wifiStatusError:
|
|
msg = p.connectError
|
|
if msg == "" {
|
|
msg = "Something went wrong. Press R to scan again."
|
|
}
|
|
c = colors.Red
|
|
default:
|
|
if len(p.networks) == 0 {
|
|
msg = "No networks found. Press R to scan again."
|
|
c = colors.Yellow
|
|
} else {
|
|
msg = "Select a network to connect — press R to rescan"
|
|
c = colors.White
|
|
}
|
|
}
|
|
|
|
if msg == "" {
|
|
return
|
|
}
|
|
|
|
item, err := NewStringItem(msg, p.applicationFont, c)
|
|
if err == nil {
|
|
p.connectionStatusItem = item
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) rebuildPasswordTexture() {
|
|
if p.passwordInputItem.ID != 0 {
|
|
gl.DeleteTextures(1, &p.passwordInputItem.ID)
|
|
}
|
|
text := p.passwordInput
|
|
if text == "" {
|
|
text = " " // prevent empty texture
|
|
}
|
|
item, err := NewStringItem(text, p.applicationFont, colors.White)
|
|
if err == nil {
|
|
p.passwordInputItem = item
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) rebuildPasswordPromptTexture() {
|
|
if p.passwordPromptItem.ID != 0 {
|
|
gl.DeleteTextures(1, &p.passwordPromptItem.ID)
|
|
}
|
|
if p.selectedNet == nil {
|
|
return
|
|
}
|
|
prompt := fmt.Sprintf("Enter password for \"%s\":", p.selectedNet.SSID)
|
|
item, err := NewStringItem(prompt, p.applicationFont, colors.White)
|
|
if err == nil {
|
|
p.passwordPromptItem = item
|
|
}
|
|
}
|
|
|
|
func (p *WiFiSetupPage) rebuildPasswordErrorTexture() {
|
|
if p.passwordErrorItem.ID != 0 {
|
|
gl.DeleteTextures(1, &p.passwordErrorItem.ID)
|
|
p.passwordErrorItem.ID = 0
|
|
}
|
|
if p.connectError == "" {
|
|
return
|
|
}
|
|
item, err := NewStringItem(p.connectError, p.applicationFont, colors.Red)
|
|
if err == nil {
|
|
p.passwordErrorItem = item
|
|
}
|
|
}
|
|
|
|
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) Render() error {
|
|
solidShader, _ := shaderManager.Get().Get("solid_color")
|
|
textShader, _ := shaderManager.Get().Get("text")
|
|
|
|
bgColor := sdl.Color{R: 0, G: 0, B: 0, A: 255}
|
|
selColor := sdl.Color{R: 68, G: 71, B: 90, A: 255}
|
|
white := colors.White
|
|
vertMargin := float32(20)
|
|
|
|
// Background
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{0, 0},
|
|
mgl32.Vec2{float32(p.ScreenWidth), float32(p.ScreenHeight)},
|
|
bgColor,
|
|
)
|
|
|
|
// Title — 48pt
|
|
titleScale := float32(48) / float32(p.BaseFontSize)
|
|
titleW := float32(p.titleItem.W) * titleScale
|
|
titleH := float32(p.titleItem.H) * titleScale
|
|
titleX := float32(p.ScreenCenterX) - titleW/2
|
|
titleY := float32(p.ScreenHeight) - titleH - 40
|
|
p.Base.RenderTexture(textShader, p.titleItem.ID,
|
|
mgl32.Vec2{titleX, titleY},
|
|
mgl32.Vec2{titleW, titleH},
|
|
white,
|
|
)
|
|
|
|
// Status message — 30pt (was 24pt, now 1.25x)
|
|
statusY := titleY - titleH - vertMargin
|
|
if p.status == wifiStatusScanning {
|
|
scale := float32(30) / float32(p.BaseFontSize)
|
|
w := float32(p.scanningStatusItem.W) * scale
|
|
h := float32(p.scanningStatusItem.H) * scale
|
|
x := float32(p.ScreenCenterX) - w/2
|
|
p.Base.RenderTexture(textShader, p.scanningStatusItem.ID,
|
|
mgl32.Vec2{x, statusY},
|
|
mgl32.Vec2{w, h},
|
|
white,
|
|
)
|
|
} else if p.connectionStatusItem.ID != 0 {
|
|
scale := float32(30) / float32(p.BaseFontSize)
|
|
w := float32(p.connectionStatusItem.W) * scale
|
|
h := float32(p.connectionStatusItem.H) * scale
|
|
x := float32(p.ScreenCenterX) - w/2
|
|
p.Base.RenderTexture(textShader, p.connectionStatusItem.ID,
|
|
mgl32.Vec2{x, statusY},
|
|
mgl32.Vec2{w, h},
|
|
p.connectionStatusItem.Color,
|
|
)
|
|
}
|
|
|
|
// Network list
|
|
listY := statusY - vertMargin
|
|
itemH := int32(40)
|
|
listPad := int32(10)
|
|
listW := float32(700)
|
|
listX := float32(p.ScreenCenterX) - listW/2
|
|
|
|
for i, item := range p.networks {
|
|
rowY := listY - float32(int32(i+1)*itemH)
|
|
|
|
if i == p.selectedIndex {
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{listX, rowY},
|
|
mgl32.Vec2{listW, float32(itemH)},
|
|
selColor,
|
|
)
|
|
}
|
|
|
|
scale := float32(24) / float32(p.BaseFontSize)
|
|
tw := float32(item.Item.W) * scale
|
|
th := float32(item.Item.H) * scale
|
|
tx := listX + float32(listPad)
|
|
ty := rowY + (float32(itemH)/2 - th/2)
|
|
p.Base.RenderTexture(textShader, item.Item.ID,
|
|
mgl32.Vec2{tx, ty},
|
|
mgl32.Vec2{tw, th},
|
|
item.Item.Color,
|
|
)
|
|
}
|
|
|
|
// Password dialog
|
|
if p.isEnteringPassword {
|
|
if err := p.renderPasswordDialog(solidShader, textShader, float32(p.BaseFontSize)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *WiFiSetupPage) renderPasswordDialog(solidShader, textShader *shaderManager.Shader, baseFontSize float32) error {
|
|
overlayColor := sdl.Color{R: 0, G: 0, B: 0, A: 180}
|
|
dialogBgColor := sdl.Color{R: 40, G: 42, B: 54, A: 255}
|
|
boxBorderColor := sdl.Color{R: 120, G: 120, B: 120, A: 255}
|
|
boxBgColor := sdl.Color{R: 30, G: 30, B: 30, A: 255}
|
|
white := colors.White
|
|
|
|
dialogW := int32(600)
|
|
dialogH := int32(250)
|
|
if p.connectError != "" {
|
|
dialogH = 310
|
|
}
|
|
dialogX := p.ScreenCenterX - dialogW/2
|
|
dialogY := p.ScreenCenterY - dialogH/2
|
|
|
|
// Overlay + dialog box
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{0, 0},
|
|
mgl32.Vec2{float32(p.ScreenWidth), float32(p.ScreenHeight)},
|
|
overlayColor,
|
|
)
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{float32(dialogX), float32(dialogY)},
|
|
mgl32.Vec2{float32(dialogW), float32(dialogH)},
|
|
dialogBgColor,
|
|
)
|
|
|
|
currentY := float32(dialogY+dialogH) - 30
|
|
|
|
// Error message
|
|
if p.passwordErrorItem.ID != 0 {
|
|
scale := float32(20) / baseFontSize
|
|
w := float32(p.passwordErrorItem.W) * scale
|
|
h := float32(p.passwordErrorItem.H) * scale
|
|
x := float32(p.ScreenCenterX) - w/2
|
|
currentY -= h
|
|
p.Base.RenderTexture(textShader, p.passwordErrorItem.ID,
|
|
mgl32.Vec2{x, currentY},
|
|
mgl32.Vec2{w, h},
|
|
colors.Red,
|
|
)
|
|
currentY -= 20
|
|
}
|
|
|
|
// Prompt
|
|
if p.passwordPromptItem.ID != 0 {
|
|
scale := float32(24) / baseFontSize
|
|
w := float32(p.passwordPromptItem.W) * scale
|
|
h := float32(p.passwordPromptItem.H) * scale
|
|
x := float32(p.ScreenCenterX) - w/2
|
|
currentY -= h
|
|
p.Base.RenderTexture(textShader, p.passwordPromptItem.ID,
|
|
mgl32.Vec2{x, currentY},
|
|
mgl32.Vec2{w, h},
|
|
white,
|
|
)
|
|
currentY -= 30
|
|
}
|
|
|
|
// Input box
|
|
padding := int32(4)
|
|
border := int32(3)
|
|
boxW := int32(500)
|
|
fontSize := int32(36)
|
|
boxH := fontSize + padding*2
|
|
bboxW := boxW + border*2
|
|
bboxH := boxH + border*2
|
|
bboxX := p.ScreenCenterX - bboxW/2
|
|
bboxY := int32(currentY) - boxH - padding*2
|
|
boxX := p.ScreenCenterX - boxW/2
|
|
boxY := int32(currentY) - boxH - padding
|
|
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{float32(bboxX), float32(bboxY)},
|
|
mgl32.Vec2{float32(bboxW), float32(bboxH)},
|
|
boxBorderColor,
|
|
)
|
|
p.Base.RenderSolidColorQuad(solidShader,
|
|
mgl32.Vec2{float32(boxX), float32(boxY)},
|
|
mgl32.Vec2{float32(boxW), float32(boxH)},
|
|
boxBgColor,
|
|
)
|
|
|
|
// Password text (plain, unmasked)
|
|
if p.passwordInputItem.ID != 0 {
|
|
scale := float32(fontSize) / baseFontSize
|
|
w := float32(p.passwordInputItem.W) * scale
|
|
h := float32(p.passwordInputItem.H) * scale
|
|
p.Base.RenderTexture(textShader, p.passwordInputItem.ID,
|
|
mgl32.Vec2{float32(boxX + padding), float32(boxY + padding)},
|
|
mgl32.Vec2{w, h},
|
|
white,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ─── Destroy ──────────────────────────────────────────────────────────────────
|
|
|
|
func (p *WiFiSetupPage) Destroy() error {
|
|
keybinds.Enabled = true
|
|
sdl.StopTextInput()
|
|
|
|
// Cancel the scan context — stops ScanForNetworks goroutine.
|
|
if p.cancel != nil {
|
|
p.cancel()
|
|
}
|
|
|
|
deleteTexture := func(id *uint32) {
|
|
if *id != 0 {
|
|
gl.DeleteTextures(1, id)
|
|
*id = 0
|
|
}
|
|
}
|
|
|
|
deleteTexture(&p.titleItem.ID)
|
|
deleteTexture(&p.scanningStatusItem.ID)
|
|
deleteTexture(&p.connectionStatusItem.ID)
|
|
deleteTexture(&p.passwordPromptItem.ID)
|
|
deleteTexture(&p.passwordInputItem.ID)
|
|
deleteTexture(&p.passwordErrorItem.ID)
|
|
|
|
for _, item := range p.networks {
|
|
deleteTexture(&item.Item.ID)
|
|
}
|
|
|
|
return p.Base.Destroy()
|
|
}
|