Compare commits

..

No commits in common. "b7b578893894da925dc983a8574a4e7028ffacfe" and "f13f13d05b9bb2f575c0e04863d34508a7176946" have entirely different histories.

175
main.go
View File

@ -655,6 +655,7 @@ func (s *appState) showPlayerView() {
centerStack := container.NewVBox(
container.NewCenter(icon),
layout.NewSpacer(),
container.NewCenter(loadBtn),
container.NewCenter(hint),
)
@ -819,9 +820,121 @@ func (s *appState) showPlayerView() {
s.setContent(mainPanel)
}
// Legacy queue view left in place but not used in player-only mode.
func (s *appState) showQueue() {}
func (s *appState) refreshQueueView() {}
func (s *appState) showQueue() {
s.stopPreview()
s.stopPlayer()
s.stopCompareSessions()
s.lastModule = s.active
s.active = "queue"
s.refreshQueueView()
}
// refreshQueueView rebuilds the queue UI while preserving scroll position and inline active conversion.
func (s *appState) refreshQueueView() {
// Preserve current scroll offset if we already have a view
if s.queueScroll != nil {
s.queueOffset = s.queueScroll.Offset
}
jobs := s.jobQueue.List()
// If a direct conversion is running but not represented in the queue, surface it as a pseudo job.
if s.convertBusy {
in := filepath.Base(s.convertActiveIn)
if in == "" && s.source != nil {
in = filepath.Base(s.source.Path)
}
out := filepath.Base(s.convertActiveOut)
jobs = append([]*queue.Job{{
ID: "active-convert",
Type: queue.JobTypeConvert,
Status: queue.JobStatusRunning,
Title: fmt.Sprintf("Direct convert: %s", in),
Description: fmt.Sprintf("Output: %s", out),
Progress: s.convertProgress,
}}, jobs...)
}
view, scroll := ui.BuildQueueView(
jobs,
func() { // onBack
if s.lastModule != "" && s.lastModule != "queue" && s.lastModule != "menu" {
s.showModule(s.lastModule)
} else {
s.showMainMenu()
}
},
func(id string) { // onPause
if err := s.jobQueue.Pause(id); err != nil {
logging.Debug(logging.CatSystem, "failed to pause job: %v", err)
}
s.refreshQueueView() // Refresh
},
func(id string) { // onResume
if err := s.jobQueue.Resume(id); err != nil {
logging.Debug(logging.CatSystem, "failed to resume job: %v", err)
}
s.refreshQueueView() // Refresh
},
func(id string) { // onCancel
if err := s.jobQueue.Cancel(id); err != nil {
logging.Debug(logging.CatSystem, "failed to cancel job: %v", err)
}
s.refreshQueueView() // Refresh
},
func(id string) { // onRemove
if err := s.jobQueue.Remove(id); err != nil {
logging.Debug(logging.CatSystem, "failed to remove job: %v", err)
}
s.refreshQueueView() // Refresh
},
func(id string) { // onMoveUp
if err := s.jobQueue.MoveUp(id); err != nil {
logging.Debug(logging.CatSystem, "failed to move job up: %v", err)
}
s.refreshQueueView() // Refresh
},
func(id string) { // onMoveDown
if err := s.jobQueue.MoveDown(id); err != nil {
logging.Debug(logging.CatSystem, "failed to move job down: %v", err)
}
s.refreshQueueView() // Refresh
},
func() { // onPauseAll
s.jobQueue.PauseAll()
s.refreshQueueView()
},
func() { // onResumeAll
s.jobQueue.ResumeAll()
s.refreshQueueView()
},
func() { // onStart
s.jobQueue.ResumeAll()
s.refreshQueueView()
},
func() { // onClear
s.jobQueue.Clear()
s.clearVideo()
s.refreshQueueView() // Refresh
},
func() { // onClearAll
s.jobQueue.ClearAll()
s.clearVideo()
s.refreshQueueView() // Refresh
},
utils.MustHex("#4CE870"), // titleColor
gridColor, // bgColor
textColor, // textColor
)
// Restore scroll offset
s.queueScroll = scroll
if s.queueScroll != nil {
s.queueScroll.Offset = s.queueOffset
s.queueScroll.Refresh()
}
s.setContent(container.NewPadded(view))
}
// addConvertToQueue adds a conversion job to the queue
func (s *appState) addConvertToQueue() error {
@ -1096,11 +1209,33 @@ func (s *appState) batchAddToQueue(paths []string) {
}
}
s.loadVideos(combined)
s.showPlayerView()
s.showModule("convert")
}
}, false)
}
func (s *appState) showConvertView(file *videoSource) {
s.stopPreview()
s.lastModule = s.active
s.active = "convert"
if file != nil {
s.source = file
}
if s.source == nil {
s.convert.OutputBase = "converted"
s.convert.CoverArtPath = ""
s.convert.AspectHandling = "Auto"
}
s.setContent(buildConvertView(s, s.source))
}
func (s *appState) showCompareView() {
s.stopPreview()
s.lastModule = s.active
s.active = "compare"
s.setContent(buildCompareView(s))
}
// jobExecutor executes a job from the queue
func (s *appState) jobExecutor(ctx context.Context, job *queue.Job, progressCallback func(float64)) error {
logging.Debug(logging.CatSystem, "executing job %s: %s", job.ID, job.Title)
@ -3804,11 +3939,10 @@ func (s *appState) handleDropPlayer(items []fyne.URI) {
var videoPaths []string
for _, uri := range items {
path := uriPath(uri)
if path == "" {
logging.Debug(logging.CatModule, "drop received empty path; uri=%v", uri)
if uri.Scheme() != "file" {
continue
}
path := uri.Path()
logging.Debug(logging.CatModule, "drop received path=%s", path)
if info, err := os.Stat(path); err == nil && info.IsDir() {
@ -3821,9 +3955,6 @@ func (s *appState) handleDropPlayer(items []fyne.URI) {
if len(videoPaths) == 0 {
logging.Debug(logging.CatUI, "no valid video files in dropped items")
fyne.Do(func() {
dialog.ShowInformation("No videos found", "Drop a video file or a folder containing videos.", s.window)
})
return
}
@ -3834,30 +3965,6 @@ func (s *appState) handleDropPlayer(items []fyne.URI) {
}
}
// uriPath extracts a usable local path from a fyne URI.
func uriPath(u fyne.URI) string {
if u == nil {
return ""
}
// Prefer Path() when present.
if p := u.Path(); p != "" {
return p
}
raw := u.String()
if raw == "" {
return ""
}
if parsed, err := url.Parse(raw); err == nil {
if parsed.Scheme == "file" {
return parsed.Path
}
}
if strings.HasPrefix(raw, "file://") {
raw = strings.TrimPrefix(raw, "file://")
}
return filepath.FromSlash(raw)
}
// detectModuleTileAtPosition calculates which module tile is at the given position
// based on the main menu grid layout (3 columns)
func (s *appState) detectModuleTileAtPosition(pos fyne.Position) string {