diff --git a/main.go b/main.go index d69ed3b..ef8db58 100644 --- a/main.go +++ b/main.go @@ -151,30 +151,31 @@ func (c convertConfig) CoverLabel() string { } type appState struct { - 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 + 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 } func (s *appState) stopPreview() { @@ -308,26 +309,11 @@ func (s *appState) applyInverseDefaults(src *videoSource) { func (s *appState) setContent(body fyne.CanvasObject) { bg := canvas.NewRectangle(backgroundColor) // Don't set a minimum size - let content determine layout naturally - - // Always use DoFromGoroutine to ensure we're on the main thread - // This is safe even when already on the main thread - app := fyne.CurrentApp() - if app != nil && app.Driver() != nil { - app.Driver().DoFromGoroutine(func() { - if body == nil { - s.window.SetContent(bg) - } else { - s.window.SetContent(container.NewMax(bg, body)) - } - }, false) - } else { - // Fallback if app not ready yet (during initialization) - if body == nil { - s.window.SetContent(bg) - } else { - s.window.SetContent(container.NewMax(bg, body)) - } + if body == nil { + s.window.SetContent(bg) + return } + s.window.SetContent(container.NewMax(bg, body)) } // showErrorWithCopy displays an error dialog with a "Copy Error" button @@ -1096,28 +1082,32 @@ func runGUI() { // Start queue processing (but paused by default) state.jobQueue.Start() - // Set callback AFTER showing the window to avoid threading issues during startup - // Use a goroutine with delay to ensure UI is fully initialized - go func() { - time.Sleep(100 * time.Millisecond) - state.jobQueue.SetChangeCallback(func() { - // Queue callbacks come from goroutines, so wrap UI calls - app := fyne.CurrentApp() - if app == nil || app.Driver() == nil { - return + // 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) + }) - app.Driver().DoFromGoroutine(func() { - // Update stats bar - state.updateStatsBar() - - // Refresh UI when queue changes - 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) {