initial commit
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
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
|
||||
textInputSupported bool
|
||||
|
||||
// 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."
|
||||
p.rebuildStatusTexture()
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Input ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error {
|
||||
switch e := (*event).(type) {
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type != sdl.KEYDOWN {
|
||||
return nil
|
||||
}
|
||||
if p.isEnteringPassword {
|
||||
p.handlePasswordKey(e.Keysym.Sym, e.Keysym.Mod)
|
||||
} else {
|
||||
p.handleNavKey(e.Keysym.Sym)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WiFiSetupPage) handlePasswordKey(sym sdl.Keycode, mod uint16) {
|
||||
switch sym {
|
||||
case sdl.K_RETURN:
|
||||
p.submitPassword()
|
||||
case sdl.K_BACKSPACE:
|
||||
if len(p.passwordInput) > 0 {
|
||||
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
|
||||
default:
|
||||
if ch := keycodeToChar(sym, mod); ch != 0 {
|
||||
p.passwordInput += string(ch)
|
||||
p.rebuildPasswordTexture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = &p.networks[p.selectedIndex].Net
|
||||
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.textInputSupported = false
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user