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 } func NewLayout(client *client.Client, playbackCtl *playback.Controller, refreshUI func()) *layout { layout := &layout{} largeView := tview.NewGrid().SetRows(0, 4, 1) smallView := tview.NewFlex().SetDirection(tview.FlexRow) layout.largeView = largeView layout.smallView = smallView pages := tview.NewPages() // 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.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)) queue.ReplaceAll(songs...) }) main.SetPlayAddSongFunc(func(song ...*subsonic.Child) { statusLine.Log("Added #%d songs to queue.", len(song)) queue.Add(song...) }) playbackCtl.SetSongElapsedFunc(func(elapsed time.Duration) { player.UpdateProgress(elapsed) queue.Update() refreshUI() }) playbackCtl.SetSongEndedFunc(func(song *subsonic.Child) { statusLine.Log("") queue.Next() if config.ScrobbleEnabled() { // Scrobble _ = client.Scrobble(song.ID) } }) queue.SetPlayFunc(func(song *subsonic.Child) { reader, _ := client.Stream(song.ID) playbackCtl.Stream(reader, song) player.SetSongInfo(song) }) 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' { queue.Next() return nil } else if event.Rune() == 'N' { queue.Prev() return nil } else if event.Rune() == 's' { playbackCtl.Stop() return nil } else if event.Rune() == 'p' { if !playbackCtl.Playing() { queue.PlayCurrent() return nil } playbackCtl.TogglePlayPause() return nil } else if event.Rune() == 'c' { playbackCtl.Stop() queue.Clear() return nil } else if event.Rune() == '/' { layout.currentFocusedView.Blur() statusLine.Search() refreshUI() return nil } 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() }