VideoTools/internal/ui/mainmenu.go
Stu Leak 87c2d28e9f Add comprehensive hardware encoder benchmarking system
Implemented a full benchmark system that automatically detects available
hardware encoders, tests them with different presets, measures FPS
performance, and recommends optimal settings for the user's system.

Features:
- Automatic test video generation (30s 1080p test pattern)
- Hardware encoder detection (NVENC, QSV, AMF, VideoToolbox)
- Comprehensive encoder testing across multiple presets
- Real-time progress UI with live results
- Performance scoring based on FPS metrics
- Top 10 results display with recommendation
- Config persistence for benchmark results
- One-click apply to use recommended settings

UI Components:
- Benchmark button in main menu header
- Progress view showing current test and results
- Final results view with ranked encoders
- Apply/Close actions for recommendation

Integration:
- Added to main menu between "Benchmark" and "Logs" buttons
- Saves results to ~/.config/VideoTools/benchmark.json
- Comprehensive debug logging for troubleshooting

This allows users to optimize their encoding settings based on their
specific hardware capabilities rather than guessing which encoder
will work best.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 09:16:36 -05:00

119 lines
3.7 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/layout"
"fyne.io/fyne/v2/widget"
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
)
// ModuleInfo contains information about a module for display
type ModuleInfo struct {
ID string
Label string
Color color.Color
Enabled bool
Category string
}
// BuildMainMenu creates the main menu view with module tiles grouped by category
func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDrop func(string, []fyne.URI), onQueueClick func(), onLogsClick func(), onBenchmarkClick func(), titleColor, queueColor, textColor color.Color, queueCompleted, queueTotal int) fyne.CanvasObject {
title := canvas.NewText("VIDEOTOOLS", titleColor)
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
title.TextSize = 28
queueTile := buildQueueTile(queueCompleted, queueTotal, queueColor, textColor, onQueueClick)
benchmarkBtn := widget.NewButton("Benchmark", onBenchmarkClick)
benchmarkBtn.Importance = widget.LowImportance
logsBtn := widget.NewButton("Logs", onLogsClick)
logsBtn.Importance = widget.LowImportance
header := container.New(layout.NewHBoxLayout(), title, layout.NewSpacer(), benchmarkBtn, logsBtn, queueTile)
categorized := map[string][]fyne.CanvasObject{}
for i := range modules {
mod := modules[i] // Create new variable for this iteration
modID := mod.ID // Capture for closure
cat := mod.Category
if cat == "" {
cat = "General"
}
var tapFunc func()
var dropFunc func([]fyne.URI)
if mod.Enabled {
// Create new closure with properly captured modID
id := modID // Explicit capture
tapFunc = func() {
onModuleClick(id)
}
dropFunc = func(items []fyne.URI) {
logging.Debug(logging.CatUI, "MainMenu dropFunc called for module=%s itemCount=%d", id, len(items))
onModuleDrop(id, items)
}
}
logging.Debug(logging.CatUI, "Creating tile for module=%s enabled=%v hasDropFunc=%v", modID, mod.Enabled, dropFunc != nil)
categorized[cat] = append(categorized[cat], buildModuleTile(mod, tapFunc, dropFunc))
}
var sections []fyne.CanvasObject
for _, cat := range sortedKeys(categorized) {
sections = append(sections,
canvas.NewText(cat, textColor),
container.NewGridWithColumns(3, categorized[cat]...),
)
}
padding := canvas.NewRectangle(color.Transparent)
padding.SetMinSize(fyne.NewSize(0, 14))
body := container.New(layout.NewVBoxLayout(),
header,
padding,
container.NewVBox(sections...),
)
return body
}
// buildModuleTile creates a single module tile
func buildModuleTile(mod ModuleInfo, tapped func(), dropped func([]fyne.URI)) fyne.CanvasObject {
logging.Debug(logging.CatUI, "building tile %s color=%v enabled=%v", mod.ID, mod.Color, mod.Enabled)
return container.NewPadded(NewModuleTile(mod.Label, mod.Color, mod.Enabled, tapped, dropped))
}
// buildQueueTile creates the queue status tile
func buildQueueTile(completed, total int, queueColor, textColor color.Color, onClick func()) fyne.CanvasObject {
rect := canvas.NewRectangle(queueColor)
rect.CornerRadius = 8
rect.SetMinSize(fyne.NewSize(160, 60))
text := canvas.NewText(fmt.Sprintf("QUEUE: %d/%d", completed, total), textColor)
text.Alignment = fyne.TextAlignCenter
text.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
text.TextSize = 18
tile := container.NewMax(rect, container.NewCenter(text))
// Make it tappable
tappable := NewTappable(tile, onClick)
return tappable
}
// sortedKeys returns sorted keys for stable category ordering
func sortedKeys(m map[string][]fyne.CanvasObject) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}