Fix author logo preview, scrolling, and icons
This commit is contained in:
parent
fb2a793c05
commit
d6305fe92f
|
|
@ -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 := 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
|
info.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
previewBox := buildMenuBox("Logo Preview", container.NewVBox(
|
logoPreviewGroup := container.NewVBox(
|
||||||
|
widget.NewLabel("Logo Preview:"),
|
||||||
logoPreviewLabel,
|
logoPreviewLabel,
|
||||||
logoPreview,
|
logoPreview,
|
||||||
logoPreviewSize,
|
logoPreviewSize,
|
||||||
))
|
)
|
||||||
|
|
||||||
menuCore := buildMenuBox("Menu Core", container.NewVBox(
|
menuCore := buildMenuBox("Menu Core", container.NewVBox(
|
||||||
createMenuCheck,
|
createMenuCheck,
|
||||||
|
|
@ -1247,6 +1248,7 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject {
|
||||||
widget.NewLabel("Logo Path:"),
|
widget.NewLabel("Logo Path:"),
|
||||||
logoLabel,
|
logoLabel,
|
||||||
logoPickButton,
|
logoPickButton,
|
||||||
|
logoPreviewGroup,
|
||||||
widget.NewLabel("Logo Position:"),
|
widget.NewLabel("Logo Position:"),
|
||||||
logoPositionSelect,
|
logoPositionSelect,
|
||||||
widget.NewLabel("Logo Scale:"),
|
widget.NewLabel("Logo Scale:"),
|
||||||
|
|
@ -1270,8 +1272,6 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject {
|
||||||
sectionGap(),
|
sectionGap(),
|
||||||
navigation,
|
navigation,
|
||||||
sectionGap(),
|
sectionGap(),
|
||||||
previewBox,
|
|
||||||
sectionGap(),
|
|
||||||
info,
|
info,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ var (
|
||||||
logger = log.New(os.Stderr, "[videotools] ", log.LstdFlags|log.Lmicroseconds)
|
logger = log.New(os.Stderr, "[videotools] ", log.LstdFlags|log.Lmicroseconds)
|
||||||
filePath string
|
filePath string
|
||||||
historyMax = 500
|
historyMax = 500
|
||||||
|
debugOn = false
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -73,6 +74,14 @@ func getStackTrace() string {
|
||||||
return string(buf[:n])
|
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)
|
// Error logs an error message with a category (always logged, even when debug is off)
|
||||||
func Error(cat Category, format string, args ...interface{}) {
|
func Error(cat Category, format string, args ...interface{}) {
|
||||||
msg := fmt.Sprintf("%s ERROR: %s", cat, fmt.Sprintf(format, args...))
|
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
|
// Debug logs a debug message with a category
|
||||||
func Debug(cat Category, format string, args ...interface{}) {
|
func Debug(cat Category, format string, args ...interface{}) {
|
||||||
|
if !debugOn {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("%s %s", cat, fmt.Sprintf(format, args...))
|
msg := fmt.Sprintf("%s %s", cat, fmt.Sprintf(format, args...))
|
||||||
timestamp := time.Now().Format(time.RFC3339Nano)
|
timestamp := time.Now().Format(time.RFC3339Nano)
|
||||||
if file != nil {
|
if file != nil {
|
||||||
|
|
@ -144,3 +156,13 @@ func Close() {
|
||||||
file.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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@ func (p *UnifiedPlayer) Load(path string, offset time.Duration) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
logging.Crash(logging.CatPlayer, "Panic in Load(): %v", r)
|
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
|
// Initialize audio context for playback
|
||||||
sampleRate := 48000
|
sampleRate := 48000
|
||||||
channels := 2
|
channels := 2
|
||||||
bytesPerSample := 2 // 16-bit = 2 bytes
|
|
||||||
|
|
||||||
ctx, ready, err := oto.NewContext(&oto.NewContextOptions{
|
ctx, ready, err := oto.NewContext(&oto.NewContextOptions{
|
||||||
SampleRate: sampleRate,
|
SampleRate: sampleRate,
|
||||||
ChannelCount: channels,
|
ChannelCount: channels,
|
||||||
|
|
@ -189,7 +187,6 @@ func (p *UnifiedPlayer) Load(path string, offset time.Duration) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// SeekToTime seeks to a specific time without restarting processes
|
// SeekToTime seeks to a specific time without restarting processes
|
||||||
func (p *UnifiedPlayer) SeekToTime(offset time.Duration) error {
|
func (p *UnifiedPlayer) SeekToTime(offset time.Duration) error {
|
||||||
|
|
@ -639,72 +636,21 @@ func (p *UnifiedPlayer) readAudioStream() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buffer := make([]byte, 4096) // 85ms chunks
|
if p.audioContext == nil {
|
||||||
|
logging.Error(logging.CatPlayer, "Audio context is not initialized")
|
||||||
for {
|
return
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if p.audioPlayer == nil {
|
if p.audioPlayer == nil {
|
||||||
p.audioPlayer = p.audioContext.NewPlayer(p.audioPipeReader)
|
p.audioPlayer = p.audioContext.NewPlayer(p.audioPipeReader)
|
||||||
p.audioPlayer.Play()
|
p.audioPlayer.Play()
|
||||||
|
logging.Info(logging.CatPlayer, "Audio player created successfully")
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
ticker := time.NewTicker(100 * time.Millisecond)
|
<-p.ctx.Done()
|
||||||
defer ticker.Stop()
|
logging.Debug(logging.CatPlayer, "Audio reading goroutine stopped")
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
logging.Debug(logging.CatPlayer, "Audio reading goroutine stopped")
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
p.updateAVSync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readVideoStream reads video frames from the video pipe
|
// readVideoStream reads video frames from the video pipe
|
||||||
|
|
|
||||||
|
|
@ -562,14 +562,8 @@ func (f *FastVScroll) CreateRenderer() fyne.WidgetRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FastVScroll) Scrolled(ev *fyne.ScrollEvent) {
|
func (f *FastVScroll) Scrolled(ev *fyne.ScrollEvent) {
|
||||||
// Multiply scroll speed by 12x for much faster navigation
|
// Increase scroll speed moderately without overshooting content bounds.
|
||||||
fastEvent := &fyne.ScrollEvent{
|
f.ScrollBy(ev.Scrolled.DY * 4.0)
|
||||||
Scrolled: fyne.Delta{
|
|
||||||
DX: ev.Scrolled.DX * 12.0,
|
|
||||||
DY: ev.Scrolled.DY * 12.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f.scroll.Scrolled(fastEvent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScrollBy scrolls the content by a delta in pixels (positive = down).
|
// 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 {
|
if f == nil || f.scroll == nil || f.scroll.Content == nil {
|
||||||
return
|
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 {
|
if max < 0 {
|
||||||
max = 0
|
max = 0
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +653,10 @@ func (d *DraggableVScroll) CreateRenderer() fyne.WidgetRenderer {
|
||||||
func (d *DraggableVScroll) Dragged(ev *fyne.DragEvent) {
|
func (d *DraggableVScroll) Dragged(ev *fyne.DragEvent) {
|
||||||
// Calculate the scroll position based on drag position
|
// Calculate the scroll position based on drag position
|
||||||
size := d.scroll.Size()
|
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 {
|
if contentSize.Height <= size.Height {
|
||||||
return // No scrolling needed
|
return // No scrolling needed
|
||||||
|
|
@ -688,7 +689,10 @@ func (d *DraggableVScroll) DragEnd() {
|
||||||
func (d *DraggableVScroll) Tapped(ev *fyne.PointEvent) {
|
func (d *DraggableVScroll) Tapped(ev *fyne.PointEvent) {
|
||||||
// Jump to tapped position
|
// Jump to tapped position
|
||||||
size := d.scroll.Size()
|
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 {
|
if contentSize.Height <= size.Height {
|
||||||
return
|
return
|
||||||
|
|
@ -711,14 +715,22 @@ func (d *DraggableVScroll) Tapped(ev *fyne.PointEvent) {
|
||||||
|
|
||||||
// Scrolled handles scroll events (mouse wheel)
|
// Scrolled handles scroll events (mouse wheel)
|
||||||
func (d *DraggableVScroll) Scrolled(ev *fyne.ScrollEvent) {
|
func (d *DraggableVScroll) Scrolled(ev *fyne.ScrollEvent) {
|
||||||
// Multiply scroll speed by 2.5x for faster scrolling
|
// Increase scroll speed modestly while clamping to content bounds.
|
||||||
fastEvent := &fyne.ScrollEvent{
|
contentSize := d.content.Size()
|
||||||
Scrolled: fyne.Delta{
|
if contentSize.Height == 0 {
|
||||||
DX: ev.Scrolled.DX * 2.5,
|
contentSize = d.content.MinSize()
|
||||||
DY: ev.Scrolled.DY * 2.5,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
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 {
|
type draggableScrollRenderer struct {
|
||||||
|
|
|
||||||
|
|
@ -285,9 +285,9 @@ func MakeIconButton(symbol, tooltip string, tapped func()) *widget.Button {
|
||||||
func LoadAppIcon() fyne.Resource {
|
func LoadAppIcon() fyne.Resource {
|
||||||
var iconFiles []string
|
var iconFiles []string
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
iconFiles = []string{"VT_Icon.ico", "VT_Icon.png", "VT_Icon.svg"}
|
iconFiles = []string{"VT_Icon.ico"}
|
||||||
} else {
|
} else {
|
||||||
iconFiles = []string{"VT_Icon.png", "VT_Icon.svg", "VT_Icon.ico"}
|
iconFiles = []string{"VT_Icon.png"}
|
||||||
}
|
}
|
||||||
var search []string
|
var search []string
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user