Simplify threading solution and add Clear All button

Simplified the approach by removing complex callback logic and using a
simple 500ms timer-based update for the stats bar instead. This eliminates
threading errors completely while keeping the code straightforward.

Changes:
1. Removed queue change callback entirely
2. Added background timer that updates stats bar every 500ms
3. Removed initComplete flag (no longer needed)
4. Simplified setContent() to direct calls
5. Added onClearAll parameter to BuildQueueView()
6. Added ClearAll() method to Queue (removes all jobs)
7. Added Clear All button with DangerImportance styling in queue view
8. Clear Completed button now has LowImportance styling

This approach is much simpler: the UI just polls the queue state
periodically instead of trying to handle callbacks from goroutines.
No more threading errors, less code, easier to understand.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Stu Leak 2025-11-27 00:25:03 -05:00
parent b80b81198f
commit fa4f4119b5
3 changed files with 60 additions and 53 deletions

View File

@ -378,6 +378,15 @@ func (q *Queue) Clear() {
q.notifyChange()
}
// ClearAll removes all jobs from the queue
func (q *Queue) ClearAll() {
q.mu.Lock()
defer q.mu.Unlock()
q.jobs = make([]*Job, 0)
q.notifyChange()
}
// generateID generates a unique ID for a job
func generateID() string {
return fmt.Sprintf("job-%d", time.Now().UnixNano())

View File

@ -21,6 +21,7 @@ func BuildQueueView(
onCancel func(string),
onRemove func(string),
onClear func(),
onClearAll func(),
titleColor, bgColor, textColor color.Color,
) fyne.CanvasObject {
// Header
@ -29,12 +30,18 @@ func BuildQueueView(
title.TextSize = 24
backBtn := widget.NewButton("← Back", onBack)
backBtn.Importance = widget.LowImportance
clearBtn := widget.NewButton("Clear Completed", onClear)
clearBtn.Importance = widget.LowImportance
clearAllBtn := widget.NewButton("Clear All", onClearAll)
clearAllBtn.Importance = widget.DangerImportance
header := container.NewBorder(
nil, nil,
backBtn,
clearBtn,
container.NewHBox(clearBtn, clearAllBtn),
container.NewCenter(title),
)

95
main.go
View File

@ -151,31 +151,30 @@ func (c convertConfig) CoverLabel() string {
}
type appState struct {
window fyne.Window
active string
initComplete bool // True after initial UI setup completes
source *videoSource
loadedVideos []*videoSource // Multiple loaded videos for navigation
currentIndex int // Current video index in loadedVideos
anim *previewAnimator
convert convertConfig
currentFrame string
player player.Controller
playerReady bool
playerVolume float64
playerMuted bool
lastVolume float64
playerPaused bool
playerPos float64
playerLast time.Time
progressQuit chan struct{}
convertCancel context.CancelFunc
playerSurf *playerSurface
convertBusy bool
convertStatus string
playSess *playSession
jobQueue *queue.Queue
statsBar *ui.ConversionStatsBar
window fyne.Window
active string
source *videoSource
loadedVideos []*videoSource // Multiple loaded videos for navigation
currentIndex int // Current video index in loadedVideos
anim *previewAnimator
convert convertConfig
currentFrame string
player player.Controller
playerReady bool
playerVolume float64
playerMuted bool
lastVolume float64
playerPaused bool
playerPos float64
playerLast time.Time
progressQuit chan struct{}
convertCancel context.CancelFunc
playerSurf *playerSurface
convertBusy bool
convertStatus string
playSess *playSession
jobQueue *queue.Queue
statsBar *ui.ConversionStatsBar
}
func (s *appState) stopPreview() {
@ -425,6 +424,10 @@ func (s *appState) showQueue() {
s.jobQueue.Clear()
s.showQueue() // Refresh
},
func() { // onClearAll
s.jobQueue.ClearAll()
s.showQueue() // Refresh
},
utils.MustHex("#4CE870"), // titleColor
gridColor, // bgColor
textColor, // textColor
@ -1082,39 +1085,27 @@ func runGUI() {
// Start queue processing (but paused by default)
state.jobQueue.Start()
// Set callback - queue changes are triggered by job processor goroutine
state.jobQueue.SetChangeCallback(func() {
// Skip updates during initialization
if !state.initComplete {
return
}
// Marshal UI updates to main thread
app := fyne.CurrentApp()
if app == nil || app.Driver() == nil {
return
}
app.Driver().DoFromGoroutine(func() {
// Update stats bar
state.updateStatsBar()
// Refresh UI when queue changes and we're on main menu
if state.active == "" {
state.showMainMenu()
}
}, false)
})
// Mark initialization as complete
state.initComplete = true
defer state.shutdown()
w.SetOnDropped(func(pos fyne.Position, items []fyne.URI) {
state.handleDrop(pos, items)
})
state.showMainMenu()
logging.Debug(logging.CatUI, "main menu rendered with %d modules", len(modulesList))
// Start stats bar update loop on a timer
go func() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
app := fyne.CurrentApp()
if app != nil && app.Driver() != nil {
app.Driver().DoFromGoroutine(func() {
state.updateStatsBar()
}, false)
}
}
}()
w.ShowAndRun()
}