From fa4f4119b5a707ae329c90c82d21d25ef6fd8c8e Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Thu, 27 Nov 2025 00:25:03 -0500 Subject: [PATCH] Simplify threading solution and add Clear All button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- internal/queue/queue.go | 9 ++++ internal/ui/queueview.go | 9 +++- main.go | 95 ++++++++++++++++++---------------------- 3 files changed, 60 insertions(+), 53 deletions(-) diff --git a/internal/queue/queue.go b/internal/queue/queue.go index dcfcd85..fd2d46b 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -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()) diff --git a/internal/ui/queueview.go b/internal/ui/queueview.go index 634fb8a..e05fb21 100644 --- a/internal/ui/queueview.go +++ b/internal/ui/queueview.go @@ -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), ) diff --git a/main.go b/main.go index ef8db58..3484bc7 100644 --- a/main.go +++ b/main.go @@ -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() }