Fix author logo preview, scrolling, and icons

This commit is contained in:
Stu Leak 2026-01-07 01:42:03 -05:00
parent a15b2668d3
commit 91c6caeaa0
5 changed files with 65 additions and 85 deletions

View File

@ -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,
)

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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