subsonic-tui/internal/tui/views/layout.go
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
Added ci workflow + fixed linting issues
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
2024-12-18 18:02:01 +02:00

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()
}