From 91c6caeaa0c6131ff41dff9d9105fbd86114a518 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 7 Jan 2026 01:42:03 -0500 Subject: [PATCH] Fix author logo preview, scrolling, and icons --- author_module.go | 8 +-- internal/logging/logging.go | 22 ++++++++ internal/player/unified_ffmpeg_player.go | 68 +++--------------------- internal/ui/components.go | 48 ++++++++++------- internal/utils/utils.go | 4 +- 5 files changed, 65 insertions(+), 85 deletions(-) diff --git a/author_module.go b/author_module.go index 3dbca6d..25fbc96 100644 --- a/author_module.go +++ b/author_module.go @@ -1223,11 +1223,12 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { info := widget.NewLabel("DVD menus are generated using the VideoTools theme and IBM Plex Mono. Menu settings apply only to disc authoring.") info.Wrapping = fyne.TextWrapWord - previewBox := buildMenuBox("Logo Preview", container.NewVBox( + logoPreviewGroup := container.NewVBox( + widget.NewLabel("Logo Preview:"), logoPreviewLabel, logoPreview, logoPreviewSize, - )) + ) menuCore := buildMenuBox("Menu Core", container.NewVBox( createMenuCheck, @@ -1247,6 +1248,7 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { widget.NewLabel("Logo Path:"), logoLabel, logoPickButton, + logoPreviewGroup, widget.NewLabel("Logo Position:"), logoPositionSelect, widget.NewLabel("Logo Scale:"), @@ -1270,8 +1272,6 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { sectionGap(), navigation, sectionGap(), - previewBox, - sectionGap(), info, ) diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 74cdbc6..b2fa45f 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -14,6 +14,7 @@ var ( logger = log.New(os.Stderr, "[videotools] ", log.LstdFlags|log.Lmicroseconds) filePath string historyMax = 500 + debugOn = false ) const ( @@ -73,6 +74,14 @@ func getStackTrace() string { return string(buf[:n]) } +// RecoverPanic logs a recovered panic with a stack trace. +// Intended for use in deferred calls inside goroutines. +func RecoverPanic() { + if r := recover(); r != nil { + Crash(CatSystem, "Recovered panic: %v", r) + } +} + // Error logs an error message with a category (always logged, even when debug is off) func Error(cat Category, format string, args ...interface{}) { msg := fmt.Sprintf("%s ERROR: %s", cat, fmt.Sprintf(format, args...)) @@ -89,6 +98,9 @@ func Error(cat Category, format string, args ...interface{}) { // Debug logs a debug message with a category func Debug(cat Category, format string, args ...interface{}) { + if !debugOn { + return + } msg := fmt.Sprintf("%s %s", cat, fmt.Sprintf(format, args...)) timestamp := time.Now().Format(time.RFC3339Nano) if file != nil { @@ -144,3 +156,13 @@ func Close() { file.Close() } } + +// SetDebug enables or disables debug logging. +func SetDebug(enabled bool) { + debugOn = enabled +} + +// FilePath returns the active log file path, if initialized. +func FilePath() string { + return filePath +} diff --git a/internal/player/unified_ffmpeg_player.go b/internal/player/unified_ffmpeg_player.go index ae5928b..3b174d5 100644 --- a/internal/player/unified_ffmpeg_player.go +++ b/internal/player/unified_ffmpeg_player.go @@ -122,7 +122,6 @@ func (p *UnifiedPlayer) Load(path string, offset time.Duration) error { defer func() { if r := recover(); r != nil { logging.Crash(logging.CatPlayer, "Panic in Load(): %v", r) - return fmt.Errorf("panic during video loading: %v", r) } }() @@ -159,8 +158,7 @@ func (p *UnifiedPlayer) Load(path string, offset time.Duration) error { // Initialize audio context for playback sampleRate := 48000 channels := 2 - bytesPerSample := 2 // 16-bit = 2 bytes - + ctx, ready, err := oto.NewContext(&oto.NewContextOptions{ SampleRate: sampleRate, ChannelCount: channels, @@ -189,7 +187,6 @@ func (p *UnifiedPlayer) Load(path string, offset time.Duration) error { return nil } -} // SeekToTime seeks to a specific time without restarting processes func (p *UnifiedPlayer) SeekToTime(offset time.Duration) error { @@ -639,72 +636,21 @@ func (p *UnifiedPlayer) readAudioStream() { } }() - buffer := make([]byte, 4096) // 85ms chunks - - for { - select { - case <-p.ctx.Done(): - logging.Debug(logging.CatPlayer, "Audio reading goroutine stopped") - return - - default: - // Read from audio pipe - n, err := p.audioPipeReader.Read(buffer) - if err != nil && err.Error() != "EOF" { - logging.Error(logging.CatPlayer, "Audio read error: %v", err) - continue - } - - if n == 0 { - continue - } - - // Initialize audio player if needed - if p.audioPlayer == nil && p.audioContext != nil { - player, err := p.audioContext.NewPlayer(p.audioPipeReader) - if err != nil { - logging.Error(logging.CatPlayer, "Failed to create audio player: %v", err) - return - } - p.audioPlayer = player - logging.Info(logging.CatPlayer, "Audio player created successfully") - } - - // Write audio data to player buffer - if p.audioPlayer != nil { - p.audioPlayer.Write(buffer[:n]) - } - - // Buffer for sync monitoring (keep small to avoid memory issues) - if len(p.audioBuffer) > 32768 { // Max 1 second at 48kHz - p.audioBuffer = p.audioBuffer[len(p.audioBuffer)-16384:] // Keep half - } - - // Simple audio sync timing - p.updateAVSync() - } + if p.audioContext == nil { + logging.Error(logging.CatPlayer, "Audio context is not initialized") + return } -} p.mu.Lock() if p.audioPlayer == nil { p.audioPlayer = p.audioContext.NewPlayer(p.audioPipeReader) p.audioPlayer.Play() + logging.Info(logging.CatPlayer, "Audio player created successfully") } p.mu.Unlock() - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-p.ctx.Done(): - logging.Debug(logging.CatPlayer, "Audio reading goroutine stopped") - return - case <-ticker.C: - p.updateAVSync() - } - } + <-p.ctx.Done() + logging.Debug(logging.CatPlayer, "Audio reading goroutine stopped") } // readVideoStream reads video frames from the video pipe diff --git a/internal/ui/components.go b/internal/ui/components.go index 52c7dfe..3dc1f4e 100644 --- a/internal/ui/components.go +++ b/internal/ui/components.go @@ -562,14 +562,8 @@ func (f *FastVScroll) CreateRenderer() fyne.WidgetRenderer { } func (f *FastVScroll) Scrolled(ev *fyne.ScrollEvent) { - // Multiply scroll speed by 12x for much faster navigation - fastEvent := &fyne.ScrollEvent{ - Scrolled: fyne.Delta{ - DX: ev.Scrolled.DX * 12.0, - DY: ev.Scrolled.DY * 12.0, - }, - } - f.scroll.Scrolled(fastEvent) + // Increase scroll speed moderately without overshooting content bounds. + f.ScrollBy(ev.Scrolled.DY * 4.0) } // ScrollBy scrolls the content by a delta in pixels (positive = down). @@ -577,7 +571,11 @@ func (f *FastVScroll) ScrollBy(delta float32) { if f == nil || f.scroll == nil || f.scroll.Content == nil { return } - max := f.scroll.Content.MinSize().Height - f.scroll.Size().Height + content := f.scroll.Content + max := content.Size().Height - f.scroll.Size().Height + if max <= 0 { + max = content.MinSize().Height - f.scroll.Size().Height + } if max < 0 { max = 0 } @@ -655,7 +653,10 @@ func (d *DraggableVScroll) CreateRenderer() fyne.WidgetRenderer { func (d *DraggableVScroll) Dragged(ev *fyne.DragEvent) { // Calculate the scroll position based on drag position size := d.scroll.Size() - contentSize := d.content.MinSize() + contentSize := d.content.Size() + if contentSize.Height == 0 { + contentSize = d.content.MinSize() + } if contentSize.Height <= size.Height { return // No scrolling needed @@ -688,7 +689,10 @@ func (d *DraggableVScroll) DragEnd() { func (d *DraggableVScroll) Tapped(ev *fyne.PointEvent) { // Jump to tapped position size := d.scroll.Size() - contentSize := d.content.MinSize() + contentSize := d.content.Size() + if contentSize.Height == 0 { + contentSize = d.content.MinSize() + } if contentSize.Height <= size.Height { return @@ -711,14 +715,22 @@ func (d *DraggableVScroll) Tapped(ev *fyne.PointEvent) { // Scrolled handles scroll events (mouse wheel) func (d *DraggableVScroll) Scrolled(ev *fyne.ScrollEvent) { - // Multiply scroll speed by 2.5x for faster scrolling - fastEvent := &fyne.ScrollEvent{ - Scrolled: fyne.Delta{ - DX: ev.Scrolled.DX * 2.5, - DY: ev.Scrolled.DY * 2.5, - }, + // Increase scroll speed modestly while clamping to content bounds. + contentSize := d.content.Size() + if contentSize.Height == 0 { + contentSize = d.content.MinSize() } - d.scroll.Scrolled(fastEvent) + max := contentSize.Height - d.scroll.Size().Height + if max < 0 { + max = 0 + } + newY := d.scroll.Offset.Y + (ev.Scrolled.DY * 2.0) + if newY < 0 { + newY = 0 + } else if newY > max { + newY = max + } + d.scroll.ScrollToOffset(fyne.NewPos(d.scroll.Offset.X, newY)) } type draggableScrollRenderer struct { diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 8708ac9..6ab7fb9 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -285,9 +285,9 @@ func MakeIconButton(symbol, tooltip string, tapped func()) *widget.Button { func LoadAppIcon() fyne.Resource { var iconFiles []string if runtime.GOOS == "windows" { - iconFiles = []string{"VT_Icon.ico", "VT_Icon.png", "VT_Icon.svg"} + iconFiles = []string{"VT_Icon.ico"} } else { - iconFiles = []string{"VT_Icon.png", "VT_Icon.svg", "VT_Icon.ico"} + iconFiles = []string{"VT_Icon.png"} } var search []string