Sagi Dayan
92e3c4ea7d
All checks were successful
ci / check for spelling errors (pull_request) Successful in 21s
ci / code quality (lint/tests) (pull_request) Successful in 2m29s
ci / Make sure build does not fail (pull_request) Successful in 2m25s
ci / notify-fail (pull_request) Has been skipped
ci / check for spelling errors (push) Successful in 22s
ci / code quality (lint/tests) (push) Successful in 1m53s
ci / Make sure build does not fail (push) Successful in 2m55s
ci / notify-fail (push) Has been skipped
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
433 lines
11 KiB
Go
433 lines
11 KiB
Go
package views
|
|
|
|
import (
|
|
"time"
|
|
|
|
"git.dayanhub.com/sagi/subsonic-tui/internal/client"
|
|
"git.dayanhub.com/sagi/subsonic-tui/internal/config"
|
|
"git.dayanhub.com/sagi/subsonic-tui/internal/playback"
|
|
"github.com/delucks/go-subsonic"
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
)
|
|
|
|
var _ View = &layout{}
|
|
|
|
type layout struct {
|
|
view *tview.Pages
|
|
player View
|
|
currentFocusedView tview.Primitive
|
|
smallView *tview.Flex
|
|
largeView *tview.Grid
|
|
status *statusLine
|
|
mainView *main
|
|
}
|
|
|
|
//gocyclo:ignore
|
|
func NewLayout(client *client.Client, playbackCtl *playback.Controller, refreshUI func()) *layout {
|
|
layout := &layout{}
|
|
|
|
largeView := tview.NewGrid().SetRows(0, 4, 1)
|
|
largeView.SetBackgroundColor(config.ColorBackground)
|
|
|
|
smallView := tview.NewFlex().SetDirection(tview.FlexRow)
|
|
smallView.SetBackgroundColor(config.ColorBackground)
|
|
|
|
layout.largeView = largeView
|
|
layout.smallView = smallView
|
|
pages := tview.NewPages()
|
|
pages.SetBackgroundColor(config.ColorBackground)
|
|
|
|
// Status / command / search
|
|
statusLine := NewStatusLine()
|
|
layout.status = statusLine
|
|
|
|
// Side Panel (Artist/Albums/Playlist)
|
|
albums := NewAlbums(client)
|
|
artists := NewArtists(client)
|
|
playlists := NewPlaylists(client)
|
|
|
|
sidePanel := tview.NewGrid().SetRows(0, 0, 0)
|
|
sidePanel.AddItem(artists.GetView(), 0, 0, 1, 1, 0, 0, false)
|
|
sidePanel.AddItem(albums.GetView(), 1, 0, 1, 1, 0, 0, false)
|
|
sidePanel.AddItem(playlists.GetView(), 2, 0, 1, 1, 0, 0, false)
|
|
|
|
// main pane
|
|
main := NewMainView(client, statusLine.Log)
|
|
layout.mainView = main
|
|
// Queue
|
|
queue := NewQueue()
|
|
|
|
// Main view
|
|
mainView := tview.NewFlex()
|
|
mainView.SetBackgroundColor(config.ColorBackground)
|
|
mainView.AddItem(sidePanel, 0, 1, false)
|
|
mainView.AddItem(main.GetView(), 0, 2, false)
|
|
mainView.AddItem(queue.GetView(), 0, 1, false)
|
|
largeView.AddItem(mainView, 0, 0, 1, 3, 0, 0, false)
|
|
// Player
|
|
player := NewPlayer(client)
|
|
largeView.AddItem(player.GetView(), 1, 0, 1, 3, 0, 0, false)
|
|
|
|
// Add status line
|
|
largeView.AddItem(statusLine.GetView(), 2, 0, 1, 3, 0, 0, false)
|
|
|
|
// Callbacks
|
|
artists.SetSelectArtistFunc(func(artistId string) {
|
|
artists.view.Blur()
|
|
statusLine.Log("Fetching artist's albums...")
|
|
|
|
go func() {
|
|
a, _ := client.GetArtist(artistId)
|
|
albums.SetAlbums(a.Album)
|
|
albums.GetView().Focus(nil)
|
|
refreshUI()
|
|
}()
|
|
})
|
|
artists.SetOpenArtistFunc(func(artistId string) {
|
|
artists.view.Blur()
|
|
statusLine.Log("Fetching artists...")
|
|
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
go func() {
|
|
a, _ := client.GetArtist(artistId)
|
|
main.SetArtist(a)
|
|
main.GetView().Focus(nil)
|
|
refreshUI()
|
|
}()
|
|
})
|
|
|
|
albums.SetCallback(func(albumID string) {
|
|
albums.view.Blur()
|
|
statusLine.Log("Fetching album...")
|
|
|
|
layout.currentFocusedView = main.GetView()
|
|
|
|
layout.rebuildSmallView()
|
|
|
|
go func() {
|
|
a, _ := client.GetAlbum(albumID)
|
|
main.SetAlbum(a)
|
|
main.view.Focus(nil)
|
|
refreshUI()
|
|
}()
|
|
})
|
|
|
|
playlists.SetCallback(func(p *subsonic.Playlist) {
|
|
playlists.view.Blur()
|
|
statusLine.Log("Fetching playlist...")
|
|
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
go func() {
|
|
playlist, _ := client.GetPlaylist(p.ID)
|
|
main.SetPlaylist(playlist)
|
|
main.view.Focus(nil)
|
|
refreshUI()
|
|
}()
|
|
})
|
|
|
|
main.SetPlayAllFunc(func(songs ...*subsonic.Child) {
|
|
statusLine.Log("Loaded #%d songs.", len(songs))
|
|
playbackCtl.SetQueue(songs)
|
|
queue.Update(songs, playbackCtl.GetQueuePosition())
|
|
})
|
|
main.SetPlayAddSongFunc(func(song ...*subsonic.Child) {
|
|
statusLine.Log("Added #%d songs to queue.", len(song))
|
|
playbackCtl.AddToQueue(song)
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
})
|
|
|
|
playbackCtl.SetSongElapsedFunc(func(song *subsonic.Child, elapsed time.Duration) {
|
|
player.SetSongInfo(song)
|
|
player.UpdateProgress(elapsed)
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
refreshUI()
|
|
})
|
|
|
|
playbackCtl.SetSongEndedFunc(func(song *subsonic.Child) {
|
|
statusLine.Log("")
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
if config.ScrobbleEnabled() {
|
|
// Scrobble
|
|
_ = client.Scrobble(song.ID)
|
|
}
|
|
})
|
|
|
|
queue.SetPlayFunc(func(position int) {
|
|
playbackCtl.SetQueuePosition(position)
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
})
|
|
|
|
statusLine.SetOnUpdateFunc(func() {
|
|
refreshUI()
|
|
})
|
|
|
|
statusLine.SetSearchFunc(func(quary string) {
|
|
if len(quary) == 0 {
|
|
layout.currentFocusedView.Focus(nil)
|
|
statusLine.Log("Search canceled")
|
|
|
|
return
|
|
}
|
|
// Search...
|
|
statusLine.Log("Searching for '%s'....", quary)
|
|
statusLine.view.Blur()
|
|
|
|
go func() {
|
|
result, _ := client.Search(quary)
|
|
|
|
layout.currentFocusedView.Blur()
|
|
main.SetSearch(result, quary)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
refreshUI()
|
|
}()
|
|
})
|
|
|
|
// Key Bindings
|
|
pages.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if statusLine.Mode() == StatusModeSearch {
|
|
return event
|
|
}
|
|
|
|
if event.Rune() == '1' {
|
|
// Focus Artists
|
|
artists.view.Blur()
|
|
albums.view.Blur()
|
|
playlists.view.Blur()
|
|
main.view.Blur()
|
|
queue.view.Blur()
|
|
artists.view.Focus(nil)
|
|
layout.currentFocusedView = artists.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return nil
|
|
} else if event.Rune() == '2' {
|
|
// Focus Albums
|
|
artists.view.Blur()
|
|
albums.view.Blur()
|
|
playlists.view.Blur()
|
|
main.view.Blur()
|
|
queue.view.Blur()
|
|
albums.view.Focus(nil)
|
|
layout.currentFocusedView = albums.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return nil
|
|
} else if event.Rune() == '3' {
|
|
// Focus Playlists
|
|
artists.view.Blur()
|
|
albums.view.Blur()
|
|
playlists.view.Blur()
|
|
main.view.Blur()
|
|
queue.view.Blur()
|
|
playlists.view.Focus(nil)
|
|
layout.currentFocusedView = playlists.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return nil
|
|
} else if event.Rune() == '`' {
|
|
// Focus Songs
|
|
artists.view.Blur()
|
|
albums.view.Blur()
|
|
playlists.view.Blur()
|
|
main.view.Blur()
|
|
queue.view.Blur()
|
|
main.view.Focus(nil)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return nil
|
|
} else if event.Rune() == '4' {
|
|
// Focus Queue
|
|
artists.view.Blur()
|
|
albums.view.Blur()
|
|
playlists.view.Blur()
|
|
main.view.Blur()
|
|
queue.view.Blur()
|
|
queue.view.Focus(nil)
|
|
layout.currentFocusedView = queue.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return nil
|
|
} else if event.Rune() == 'n' {
|
|
playbackCtl.Next()
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
return nil
|
|
} else if event.Rune() == 'N' {
|
|
playbackCtl.Prev()
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
return nil
|
|
} else if event.Rune() == 's' {
|
|
playbackCtl.Stop()
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
return nil
|
|
} else if event.Rune() == 'p' {
|
|
if playbackCtl.State() == playback.PlaybackStateStopped {
|
|
song := playbackCtl.GetCurrentSong()
|
|
if song != nil {
|
|
playbackCtl.Play(song)
|
|
}
|
|
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
return nil
|
|
}
|
|
|
|
playbackCtl.TogglePlayPause()
|
|
|
|
return nil
|
|
} else if event.Rune() == 'c' {
|
|
playbackCtl.ClearQueue()
|
|
queue.Update(playbackCtl.GetQueue(), playbackCtl.GetQueuePosition())
|
|
|
|
return nil
|
|
} else if event.Rune() == '/' {
|
|
layout.currentFocusedView.Blur()
|
|
statusLine.Search()
|
|
refreshUI()
|
|
|
|
return nil
|
|
} else if event.Rune() == 'L' {
|
|
if layout.currentFocusedView == albums.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
main.view.Focus(nil)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == artists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
main.view.Focus(nil)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == playlists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
main.view.Focus(nil)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == main.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
queue.view.Focus(nil)
|
|
layout.currentFocusedView = queue.GetView()
|
|
layout.rebuildSmallView()
|
|
}
|
|
} else if event.Rune() == 'H' {
|
|
if layout.currentFocusedView == queue.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
main.view.Focus(nil)
|
|
layout.currentFocusedView = main.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == main.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
artists.view.Focus(nil)
|
|
layout.currentFocusedView = artists.GetView()
|
|
layout.rebuildSmallView()
|
|
}
|
|
} else if event.Rune() == 'J' {
|
|
if layout.currentFocusedView == albums.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
playlists.view.Focus(nil)
|
|
layout.currentFocusedView = playlists.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == artists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
albums.view.Focus(nil)
|
|
layout.currentFocusedView = albums.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == playlists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
artists.view.Focus(nil)
|
|
layout.currentFocusedView = artists.GetView()
|
|
layout.rebuildSmallView()
|
|
}
|
|
} else if event.Rune() == 'K' {
|
|
if layout.currentFocusedView == albums.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
artists.view.Focus(nil)
|
|
layout.currentFocusedView = artists.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == artists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
playlists.view.Focus(nil)
|
|
layout.currentFocusedView = playlists.GetView()
|
|
layout.rebuildSmallView()
|
|
} else if layout.currentFocusedView == playlists.GetView() {
|
|
layout.currentFocusedView.Blur()
|
|
albums.view.Focus(nil)
|
|
layout.currentFocusedView = albums.GetView()
|
|
layout.rebuildSmallView()
|
|
}
|
|
}
|
|
|
|
return event
|
|
})
|
|
|
|
largeView.SetFocusFunc(func() {
|
|
largeView.Blur()
|
|
layout.currentFocusedView.Focus(nil)
|
|
})
|
|
smallView.SetFocusFunc(func() {
|
|
smallView.Blur()
|
|
layout.currentFocusedView.Focus(nil)
|
|
})
|
|
|
|
statusLine.Log("Press '?' for help")
|
|
|
|
pages.AddPage("small", smallView, true, false)
|
|
pages.AddPage("large", largeView, true, true)
|
|
|
|
layout.view = pages
|
|
layout.player = player
|
|
// Auto focus on artists
|
|
artists.GetView().Focus(nil)
|
|
layout.currentFocusedView = artists.GetView()
|
|
layout.rebuildSmallView()
|
|
|
|
return layout
|
|
}
|
|
|
|
func (l *layout) rebuildSmallView() {
|
|
l.smallView.Clear()
|
|
l.smallView.AddItem(l.currentFocusedView, 0, 1, false)
|
|
l.smallView.AddItem(l.player.GetView(), 4, 0, false)
|
|
l.smallView.AddItem(l.status.GetView(), 1, 0, false)
|
|
}
|
|
|
|
func (l *layout) Mode() Statusmode {
|
|
return l.status.Mode()
|
|
}
|
|
|
|
func (l *layout) GetView() tview.Primitive {
|
|
return l.view
|
|
}
|
|
|
|
func (l *layout) Update() {
|
|
_, _, w, h := l.view.GetRect()
|
|
page, _ := l.view.GetFrontPage()
|
|
smallView := w < 100 || h < 30
|
|
|
|
if smallView {
|
|
if page != "small" {
|
|
l.mainView.SetMiniView(true)
|
|
go l.view.SwitchToPage("small")
|
|
|
|
return
|
|
}
|
|
} else {
|
|
if page != "large" {
|
|
l.mainView.SetMiniView(false)
|
|
go l.view.SwitchToPage("large")
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
l.player.Update()
|
|
}
|