504 lines
13 KiB
Go
504 lines
13 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"sort"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/widget"
|
|
"git.leaktechnologies.dev/stu/VideoTools/internal/benchmark"
|
|
"git.leaktechnologies.dev/stu/VideoTools/internal/sysinfo"
|
|
)
|
|
|
|
// BuildBenchmarkProgressView creates the benchmark progress UI
|
|
func BuildBenchmarkProgressView(
|
|
hwInfo sysinfo.HardwareInfo,
|
|
onCancel func(),
|
|
titleColor, bgColor, textColor color.Color,
|
|
) *BenchmarkProgressView {
|
|
view := &BenchmarkProgressView{
|
|
hwInfo: hwInfo,
|
|
titleColor: titleColor,
|
|
bgColor: bgColor,
|
|
textColor: textColor,
|
|
onCancel: onCancel,
|
|
}
|
|
view.build()
|
|
return view
|
|
}
|
|
|
|
// BenchmarkProgressView shows real-time benchmark progress
|
|
type BenchmarkProgressView struct {
|
|
hwInfo sysinfo.HardwareInfo
|
|
titleColor color.Color
|
|
bgColor color.Color
|
|
textColor color.Color
|
|
onCancel func()
|
|
|
|
container *fyne.Container
|
|
statusLabel *widget.Label
|
|
progressBar *widget.ProgressBar
|
|
currentLabel *widget.Label
|
|
resultsBox *fyne.Container
|
|
cancelBtn *widget.Button
|
|
}
|
|
|
|
func (v *BenchmarkProgressView) build() {
|
|
// Header
|
|
title := canvas.NewText("ENCODER BENCHMARK", v.titleColor)
|
|
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
|
title.TextSize = 24
|
|
|
|
v.cancelBtn = widget.NewButton("Cancel", v.onCancel)
|
|
v.cancelBtn.Importance = widget.DangerImportance
|
|
|
|
header := container.NewBorder(
|
|
nil, nil,
|
|
nil,
|
|
v.cancelBtn,
|
|
container.NewCenter(title),
|
|
)
|
|
|
|
// Hardware info section
|
|
hwInfoTitle := widget.NewLabel("System Hardware")
|
|
hwInfoTitle.TextStyle = fyne.TextStyle{Bold: true}
|
|
hwInfoTitle.Alignment = fyne.TextAlignCenter
|
|
|
|
cpuLabel := widget.NewLabel(fmt.Sprintf("CPU: %s (%d cores @ %s)", v.hwInfo.CPU, v.hwInfo.CPUCores, v.hwInfo.CPUMHz))
|
|
cpuLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
gpuLabel := widget.NewLabel(fmt.Sprintf("GPU: %s", v.hwInfo.GPU))
|
|
gpuLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
ramLabel := widget.NewLabel(fmt.Sprintf("RAM: %s", v.hwInfo.RAM))
|
|
|
|
driverLabel := widget.NewLabel(fmt.Sprintf("Driver: %s", v.hwInfo.GPUDriver))
|
|
driverLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
hwCard := canvas.NewRectangle(color.RGBA{R: 34, G: 38, B: 48, A: 255})
|
|
hwCard.CornerRadius = 8
|
|
|
|
hwContent := container.NewVBox(
|
|
hwInfoTitle,
|
|
cpuLabel,
|
|
gpuLabel,
|
|
ramLabel,
|
|
driverLabel,
|
|
)
|
|
|
|
hwInfoSection := container.NewPadded(
|
|
container.NewMax(hwCard, hwContent),
|
|
)
|
|
|
|
// Status section
|
|
v.statusLabel = widget.NewLabel("Initializing benchmark...")
|
|
v.statusLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
v.statusLabel.Alignment = fyne.TextAlignCenter
|
|
|
|
v.progressBar = widget.NewProgressBar()
|
|
v.progressBar.Min = 0
|
|
v.progressBar.Max = 100
|
|
|
|
v.currentLabel = widget.NewLabel("")
|
|
v.currentLabel.Alignment = fyne.TextAlignCenter
|
|
v.currentLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
statusSection := container.NewVBox(
|
|
v.statusLabel,
|
|
v.progressBar,
|
|
v.currentLabel,
|
|
)
|
|
|
|
// Results section
|
|
resultsTitle := widget.NewLabel("Results")
|
|
resultsTitle.TextStyle = fyne.TextStyle{Bold: true}
|
|
resultsTitle.Alignment = fyne.TextAlignCenter
|
|
|
|
v.resultsBox = container.NewVBox()
|
|
resultsScroll := container.NewVScroll(v.resultsBox)
|
|
resultsScroll.SetMinSize(fyne.NewSize(0, 300))
|
|
|
|
resultsSection := container.NewBorder(
|
|
resultsTitle,
|
|
nil, nil, nil,
|
|
resultsScroll,
|
|
)
|
|
|
|
// Main layout
|
|
body := container.NewBorder(
|
|
header,
|
|
nil, nil, nil,
|
|
container.NewVBox(
|
|
hwInfoSection,
|
|
widget.NewSeparator(),
|
|
statusSection,
|
|
widget.NewSeparator(),
|
|
resultsSection,
|
|
),
|
|
)
|
|
|
|
v.container = container.NewPadded(body)
|
|
}
|
|
|
|
// GetContainer returns the main container
|
|
func (v *BenchmarkProgressView) GetContainer() *fyne.Container {
|
|
return v.container
|
|
}
|
|
|
|
// UpdateProgress updates the progress bar and labels
|
|
func (v *BenchmarkProgressView) UpdateProgress(current, total int, encoder, preset string) {
|
|
pct := (float64(current) / float64(total)) * 100 // Convert to 0-100 range
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
v.progressBar.SetValue(pct)
|
|
v.statusLabel.SetText(fmt.Sprintf("Testing encoder %d of %d", current, total))
|
|
v.currentLabel.SetText(fmt.Sprintf("Testing: %s (preset: %s)", encoder, preset))
|
|
v.progressBar.Refresh()
|
|
v.statusLabel.Refresh()
|
|
v.currentLabel.Refresh()
|
|
}, false)
|
|
}
|
|
|
|
// AddResult adds a completed test result to the display
|
|
func (v *BenchmarkProgressView) AddResult(result benchmark.Result) {
|
|
var statusColor color.Color
|
|
var statusText string
|
|
|
|
if result.Error != "" {
|
|
statusColor = color.RGBA{R: 255, G: 68, B: 68, A: 255} // Red
|
|
statusText = fmt.Sprintf("FAILED: %s", result.Error)
|
|
} else {
|
|
statusColor = color.RGBA{R: 76, G: 232, B: 112, A: 255} // Green
|
|
statusText = fmt.Sprintf("%.1f FPS | %.1fs encoding time", result.FPS, result.EncodingTime)
|
|
}
|
|
|
|
// Status indicator
|
|
statusRect := canvas.NewRectangle(statusColor)
|
|
statusRect.SetMinSize(fyne.NewSize(6, 0))
|
|
|
|
// Encoder label
|
|
encoderLabel := widget.NewLabel(fmt.Sprintf("%s (%s)", result.Encoder, result.Preset))
|
|
encoderLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
// Status label
|
|
statusLabel := widget.NewLabel(statusText)
|
|
statusLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
// Card content
|
|
content := container.NewBorder(
|
|
nil, nil,
|
|
statusRect,
|
|
nil,
|
|
container.NewVBox(encoderLabel, statusLabel),
|
|
)
|
|
|
|
// Card background
|
|
card := canvas.NewRectangle(v.bgColor)
|
|
card.CornerRadius = 4
|
|
|
|
item := container.NewPadded(
|
|
container.NewMax(card, content),
|
|
)
|
|
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
v.resultsBox.Add(item)
|
|
v.resultsBox.Refresh()
|
|
}, false)
|
|
}
|
|
|
|
// SetComplete marks the benchmark as complete
|
|
func (v *BenchmarkProgressView) SetComplete() {
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
v.statusLabel.SetText("Benchmark complete!")
|
|
v.progressBar.SetValue(100.0)
|
|
v.currentLabel.SetText("")
|
|
v.cancelBtn.SetText("Close")
|
|
v.statusLabel.Refresh()
|
|
v.progressBar.Refresh()
|
|
v.currentLabel.Refresh()
|
|
v.cancelBtn.Refresh()
|
|
}, false)
|
|
}
|
|
|
|
// BuildBenchmarkResultsView creates the final results/recommendation UI
|
|
func BuildBenchmarkResultsView(
|
|
results []benchmark.Result,
|
|
recommendation benchmark.Result,
|
|
hwInfo sysinfo.HardwareInfo,
|
|
onApply func(),
|
|
onClose func(),
|
|
titleColor, bgColor, textColor color.Color,
|
|
) fyne.CanvasObject {
|
|
// Header
|
|
title := canvas.NewText("BENCHMARK RESULTS", titleColor)
|
|
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
|
title.TextSize = 24
|
|
|
|
closeBtn := widget.NewButton("Close", onClose)
|
|
closeBtn.Importance = widget.LowImportance
|
|
|
|
header := container.NewBorder(
|
|
nil, nil,
|
|
nil,
|
|
closeBtn,
|
|
container.NewCenter(title),
|
|
)
|
|
|
|
// Hardware info section
|
|
hwInfoTitle := widget.NewLabel("System Hardware")
|
|
hwInfoTitle.TextStyle = fyne.TextStyle{Bold: true}
|
|
hwInfoTitle.Alignment = fyne.TextAlignCenter
|
|
|
|
cpuLabel := widget.NewLabel(fmt.Sprintf("CPU: %s (%d cores @ %s)", hwInfo.CPU, hwInfo.CPUCores, hwInfo.CPUMHz))
|
|
cpuLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
gpuLabel := widget.NewLabel(fmt.Sprintf("GPU: %s", hwInfo.GPU))
|
|
gpuLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
ramLabel := widget.NewLabel(fmt.Sprintf("RAM: %s", hwInfo.RAM))
|
|
|
|
driverLabel := widget.NewLabel(fmt.Sprintf("Driver: %s", hwInfo.GPUDriver))
|
|
driverLabel.Wrapping = fyne.TextWrapWord
|
|
|
|
hwCard := canvas.NewRectangle(color.RGBA{R: 34, G: 38, B: 48, A: 255})
|
|
hwCard.CornerRadius = 8
|
|
|
|
hwContent := container.NewVBox(
|
|
hwInfoTitle,
|
|
cpuLabel,
|
|
gpuLabel,
|
|
ramLabel,
|
|
driverLabel,
|
|
)
|
|
|
|
hwInfoSection := container.NewPadded(
|
|
container.NewMax(hwCard, hwContent),
|
|
)
|
|
|
|
// Recommendation section
|
|
if recommendation.Encoder != "" {
|
|
recTitle := widget.NewLabel("RECOMMENDED ENCODER")
|
|
recTitle.TextStyle = fyne.TextStyle{Bold: true}
|
|
recTitle.Alignment = fyne.TextAlignCenter
|
|
|
|
recEncoder := widget.NewLabel(fmt.Sprintf("%s (preset: %s)", recommendation.Encoder, recommendation.Preset))
|
|
recEncoder.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
|
recEncoder.Alignment = fyne.TextAlignCenter
|
|
|
|
recStats := widget.NewLabel(fmt.Sprintf("%.1f FPS | %.1fs encoding time | Score: %.1f",
|
|
recommendation.FPS, recommendation.EncodingTime, recommendation.Score))
|
|
recStats.Alignment = fyne.TextAlignCenter
|
|
|
|
applyBtn := widget.NewButton("Apply to Settings", onApply)
|
|
applyBtn.Importance = widget.HighImportance
|
|
|
|
recCard := canvas.NewRectangle(color.RGBA{R: 68, G: 136, B: 255, A: 50})
|
|
recCard.CornerRadius = 8
|
|
|
|
recContent := container.NewVBox(
|
|
recTitle,
|
|
recEncoder,
|
|
recStats,
|
|
container.NewCenter(applyBtn),
|
|
)
|
|
|
|
recommendationSection := container.NewPadded(
|
|
container.NewMax(recCard, recContent),
|
|
)
|
|
|
|
// Top results list
|
|
topResultsTitle := widget.NewLabel("Top Encoders")
|
|
topResultsTitle.TextStyle = fyne.TextStyle{Bold: true}
|
|
topResultsTitle.Alignment = fyne.TextAlignCenter
|
|
|
|
var filtered []benchmark.Result
|
|
for _, result := range results {
|
|
if result.Error == "" {
|
|
filtered = append(filtered, result)
|
|
}
|
|
}
|
|
|
|
sort.Slice(filtered, func(i, j int) bool {
|
|
return filtered[i].Score > filtered[j].Score
|
|
})
|
|
|
|
var resultItems []fyne.CanvasObject
|
|
for i, result := range filtered {
|
|
rankLabel := widget.NewLabel(fmt.Sprintf("#%d", i+1))
|
|
rankLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
encoderLabel := widget.NewLabel(fmt.Sprintf("%s (%s)", result.Encoder, result.Preset))
|
|
|
|
statsLabel := widget.NewLabel(fmt.Sprintf("%.1f FPS | %.1fs | Score: %.1f",
|
|
result.FPS, result.EncodingTime, result.Score))
|
|
statsLabel.TextStyle = fyne.TextStyle{Italic: true}
|
|
|
|
content := container.NewBorder(
|
|
nil, nil,
|
|
rankLabel,
|
|
nil,
|
|
container.NewVBox(encoderLabel, statsLabel),
|
|
)
|
|
|
|
card := canvas.NewRectangle(bgColor)
|
|
card.CornerRadius = 4
|
|
|
|
item := container.NewPadded(
|
|
container.NewMax(card, content),
|
|
)
|
|
|
|
resultItems = append(resultItems, item)
|
|
}
|
|
|
|
resultsBox := container.NewVBox(resultItems...)
|
|
resultsScroll := container.NewVScroll(resultsBox)
|
|
resultsScroll.SetMinSize(fyne.NewSize(0, 300))
|
|
|
|
resultsSection := container.NewBorder(
|
|
topResultsTitle,
|
|
nil, nil, nil,
|
|
resultsScroll,
|
|
)
|
|
|
|
// Main layout
|
|
body := container.NewBorder(
|
|
header,
|
|
nil, nil, nil,
|
|
container.NewVBox(
|
|
hwInfoSection,
|
|
widget.NewSeparator(),
|
|
recommendationSection,
|
|
widget.NewSeparator(),
|
|
resultsSection,
|
|
),
|
|
)
|
|
|
|
return container.NewPadded(body)
|
|
}
|
|
|
|
// No results case
|
|
emptyMsg := widget.NewLabel("No benchmark results available")
|
|
emptyMsg.Alignment = fyne.TextAlignCenter
|
|
|
|
body := container.NewBorder(
|
|
header,
|
|
nil, nil, nil,
|
|
container.NewCenter(emptyMsg),
|
|
)
|
|
|
|
return container.NewPadded(body)
|
|
}
|
|
|
|
// BuildBenchmarkHistoryView creates the benchmark history browser UI
|
|
func BuildBenchmarkHistoryView(
|
|
history []BenchmarkHistoryRun,
|
|
onSelectRun func(int),
|
|
onClose func(),
|
|
titleColor, bgColor, textColor color.Color,
|
|
) fyne.CanvasObject {
|
|
// Header
|
|
title := canvas.NewText("BENCHMARK HISTORY", titleColor)
|
|
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
|
title.TextSize = 24
|
|
|
|
closeBtn := widget.NewButton("← Back", onClose)
|
|
closeBtn.Importance = widget.LowImportance
|
|
|
|
header := container.NewBorder(
|
|
nil, nil,
|
|
closeBtn,
|
|
nil,
|
|
container.NewCenter(title),
|
|
)
|
|
|
|
if len(history) == 0 {
|
|
emptyMsg := widget.NewLabel("No benchmark history yet.\n\nRun your first benchmark to see results here.")
|
|
emptyMsg.Alignment = fyne.TextAlignCenter
|
|
emptyMsg.Wrapping = fyne.TextWrapWord
|
|
|
|
body := container.NewBorder(
|
|
header,
|
|
nil, nil, nil,
|
|
container.NewCenter(emptyMsg),
|
|
)
|
|
|
|
return container.NewPadded(body)
|
|
}
|
|
|
|
// Build list of benchmark runs
|
|
var runItems []fyne.CanvasObject
|
|
for i, run := range history {
|
|
idx := i // Capture for closure
|
|
runItems = append(runItems, buildHistoryRunItem(run, idx, onSelectRun, bgColor, textColor))
|
|
}
|
|
|
|
runsList := container.NewVBox(runItems...)
|
|
runsScroll := container.NewVScroll(runsList)
|
|
runsScroll.SetMinSize(fyne.NewSize(0, 400))
|
|
|
|
infoLabel := widget.NewLabel("Click on a benchmark run to view detailed results")
|
|
infoLabel.Alignment = fyne.TextAlignCenter
|
|
infoLabel.TextStyle = fyne.TextStyle{Italic: true}
|
|
|
|
body := container.NewBorder(
|
|
header,
|
|
container.NewVBox(widget.NewSeparator(), infoLabel),
|
|
nil, nil,
|
|
runsScroll,
|
|
)
|
|
|
|
return container.NewPadded(body)
|
|
}
|
|
|
|
// BenchmarkHistoryRun represents a benchmark run in the history view
|
|
type BenchmarkHistoryRun struct {
|
|
Timestamp string
|
|
ResultCount int
|
|
RecommendedEncoder string
|
|
RecommendedPreset string
|
|
RecommendedFPS float64
|
|
}
|
|
|
|
func buildHistoryRunItem(
|
|
run BenchmarkHistoryRun,
|
|
index int,
|
|
onSelect func(int),
|
|
bgColor, textColor color.Color,
|
|
) fyne.CanvasObject {
|
|
// Timestamp label
|
|
timeLabel := widget.NewLabel(run.Timestamp)
|
|
timeLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
// Recommendation info
|
|
recLabel := widget.NewLabel(fmt.Sprintf("Recommended: %s (%s) - %.1f FPS",
|
|
run.RecommendedEncoder, run.RecommendedPreset, run.RecommendedFPS))
|
|
|
|
// Result count
|
|
countLabel := widget.NewLabel(fmt.Sprintf("%d encoders tested", run.ResultCount))
|
|
countLabel.TextStyle = fyne.TextStyle{Italic: true}
|
|
|
|
// Content
|
|
content := container.NewVBox(
|
|
timeLabel,
|
|
recLabel,
|
|
countLabel,
|
|
)
|
|
|
|
// Card background
|
|
card := canvas.NewRectangle(bgColor)
|
|
card.CornerRadius = 4
|
|
|
|
item := container.NewPadded(
|
|
container.NewMax(card, content),
|
|
)
|
|
|
|
// Make it tappable
|
|
tappable := NewTappable(item, func() {
|
|
onSelect(index)
|
|
})
|
|
|
|
return tappable
|
|
}
|