Files
raspscreen/src/draw.go
DaanSelen ab1b46d71c
Some checks failed
Cross-Compile Binaries / compile-linux (push) Failing after 3m4s
Cross-Compile Binaries / compile-windows (push) Has been cancelled
chore: reorder and expand script
2026-01-13 10:49:20 +01:00

313 lines
7.3 KiB
Go

package main
import (
"image/color"
"log"
"path/filepath"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
)
type FlashKind int
const (
FlashError FlashKind = iota
FlashSuccess
)
var (
presFileFilter []string = []string{".pptx", ".odp"}
videoFileFilter []string = []string{".mp4", ".mkv", ".mov", ".webm"}
flashTimers = map[*widget.Button]*time.Timer{}
)
func flashColor(btn *widget.Button, kind FlashKind) {
if t, ok := flashTimers[btn]; ok {
t.Stop() // cancel previous flash
}
duration := 1 * time.Second // default flash duration
// Determine button style based on kind
var importance widget.Importance
switch kind {
case FlashError:
importance = widget.DangerImportance
case FlashSuccess:
importance = widget.SuccessImportance
duration = 3 * time.Second
}
// Apply the flash immediately on UI thread
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
btn.Importance = importance
btn.Refresh()
}, false)
// Schedule reset after duration (non-blocking)
flashTimers[btn] = time.AfterFunc(duration, func() {
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
btn.Importance = widget.LowImportance
btn.Refresh()
}, false)
})
}
func drawSeparator(trailNewLine bool) *fyne.Container {
emptyLabel := widget.NewLabel("") // Give it some space at the bottom with an empty line
separatorLine := canvas.NewLine(color.Gray{Y: 128})
var separContainer *fyne.Container
if trailNewLine {
separContainer = container.NewVBox(
emptyLabel,
separatorLine,
emptyLabel,
)
} else {
separContainer = container.NewVBox(
emptyLabel,
separatorLine,
)
}
return separContainer
}
func drawModeRow(targetMode *int) *fyne.Container {
actionText := widget.NewLabel("Select Mode")
// targetMode is defined as:
// 0 undefined / not used
// 1 presentation (PowerPoint, Impress)
// 2 video (mp4, mkv)
var presModeBtn *widget.Button
var videoModeBtn *widget.Button
presModeBtn = widget.NewButton("Presentation", func() {
*targetMode = 1
presModeBtn.Importance = widget.HighImportance
videoModeBtn.Importance = widget.LowImportance
log.Println("Set mode to presentation", *targetMode)
refreshButtons(presModeBtn, videoModeBtn)
})
presWide := container.NewGridWrap(
buttonSize,
presModeBtn,
)
videoModeBtn = widget.NewButton("Video", func() {
*targetMode = 2
presModeBtn.Importance = widget.LowImportance
videoModeBtn.Importance = widget.HighImportance
log.Println("Set mode to video", *targetMode)
refreshButtons(presModeBtn, videoModeBtn)
})
videoWide := container.NewGridWrap(
buttonSize,
videoModeBtn,
)
modeRow := container.NewHBox(
presWide,
widget.NewLabel(""),
videoWide,
)
modeCol := container.NewVBox(
actionText,
modeRow,
)
return modeCol
}
func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, reloadBtn *widget.Button, cfg RaspiConfig) *fyne.Container {
actionText := widget.NewLabel("Select Target")
var previousTarget string
var verifyBtn *widget.Button
// Left side for selection of target
piSelection := widget.NewRadioGroup(raspiNames, func(selected string) {
if selected == previousTarget {
return // nothing changed
}
previousTarget = selected
*raspiTarget = selected
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
})
selecCol := container.NewVBox(
actionText,
piSelection,
)
var credOK bool
verifyBtn = widget.NewButton("Check Connection", func() {
verifyBtn.Importance = widget.HighImportance
refreshButtons(verifyBtn)
log.Println("Verifying credentials...")
uploadBtn.Disable()
reloadBtn.Disable()
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
go func() {
credOK = verifyCred(*raspiTarget, cfg)
if credOK {
// Must update UI on main thread
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
flashColor(verifyBtn, FlashSuccess) // flashcolor should handle RunOnMain internally
uploadBtn.Enable()
reloadBtn.Enable()
}, false)
} else {
flashColor(verifyBtn, FlashError) // flashcolor should handle RunOnMain internally
}
}()
})
verifyWide := container.NewGridWrap(
buttonSize,
verifyBtn,
)
wholeCol := container.NewVBox(
selecCol,
widget.NewLabel(""),
verifyWide,
)
return wholeCol
}
func drawFileSelection(localPath *string, targetMode *int, parentWindow fyne.Window) *fyne.Container {
actionText := widget.NewLabel("Select File")
pathLabel := widget.NewLabel("No File Selected Yet...")
var uploadBtn *widget.Button
uploadBtn = widget.NewButton("Click to Select File", func() {
// Stupid dialog error on 'headless' servers...
log.Println("Ignore the next error. ~Refer: https://github.com/fyne-io/fyne/issues/4110")
// Use NewFileOpen to get the dialog object
uploadSelecDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
if r != nil {
defer r.Close()
pathLabel.SetText(r.URI().Name())
*localPath = r.URI().Path()
uploadBtn.Importance = widget.HighImportance
refreshButtons(uploadBtn)
log.Println("Local path of selected file:", *localPath)
}
}, parentWindow)
var fileFilter storage.FileFilter
switch *targetMode {
case 1:
fileFilter = storage.NewExtensionFileFilter(presFileFilter)
case 2:
fileFilter = storage.NewExtensionFileFilter(videoFileFilter)
}
uploadSelecDiag.SetFilter(fileFilter)
// Resize the dialog to the whole window, if it wants to do that -
uploadSelecDiag.Resize(windowSize)
uploadSelecDiag.Show()
})
uploadWide := container.NewGridWrap(
buttonSize,
uploadBtn,
)
fileSelecRow := container.NewHBox(
uploadWide,
pathLabel,
)
fileSelecCol := container.NewVBox(
actionText,
fileSelecRow,
)
return fileSelecCol
}
// targetMode *int
func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, targetMode *int, cfg RaspiConfig) (*fyne.Container, *widget.Button, *widget.Button) {
fExt := filepath.Ext(*localUploadPath)
remotePresPath := "/opt/akartontv/media" + fExt
// Configuration of the bottom of the application
var uploadBtn *widget.Button
uploadBtn = widget.NewButton("Upload File", func() {
uploadBtn.Importance = widget.HighImportance
refreshButtons(uploadBtn)
go func() {
ok := sftpUploadFile(*raspiTarget, *localUploadPath, remotePresPath, cfg)
if ok {
flashColor(uploadBtn, FlashSuccess)
} else {
flashColor(uploadBtn, FlashError)
}
}()
})
uploadWide := container.NewGridWrap(
dButtonSize,
uploadBtn,
)
var reloadBtn *widget.Button
reloadBtn = widget.NewButton("Restart Program", func() {
reloadBtn.Importance = widget.HighImportance
refreshButtons(uploadBtn)
go func() {
ok := restartShow(*raspiTarget, *targetMode, cfg)
if ok {
flashColor(reloadBtn, FlashSuccess)
} else {
flashColor(reloadBtn, FlashError)
}
}()
})
reloadWide := container.NewGridWrap(
dButtonSize,
reloadBtn,
)
cancelBtn := widget.NewButton("Exit", func() {
log.Println("Quitting")
app.Quit()
})
cancelWide := container.NewGridWrap(
buttonSize,
cancelBtn,
)
bottom := container.NewHBox(
uploadWide, // left
reloadWide, // also next to the one above
layout.NewSpacer(), // flexible space
cancelWide, // right
)
uploadBtn.Disable()
reloadBtn.Disable()
return bottom, uploadBtn, reloadBtn
}