358 lines
10 KiB
Go
358 lines
10 KiB
Go
package gui
|
|
|
|
import (
|
|
"log"
|
|
"math"
|
|
|
|
"github.com/diamondburned/gotk4-adwaita/pkg/adw"
|
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
|
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
|
"suruatoel.xyz/mcg/data"
|
|
"suruatoel.xyz/mcg/internal/client"
|
|
"suruatoel.xyz/mcg/internal/common"
|
|
)
|
|
|
|
type playlistPanel struct {
|
|
*adw.Bin
|
|
toolbar *gtk.Box
|
|
selectButton *gtk.ToggleButton
|
|
actionbarRevealer *gtk.Revealer
|
|
clearButton *gtk.Button
|
|
playlistGrid *gtk.GridView
|
|
gridFactory *pictureItemFactory[string]
|
|
playlistModel *gtk.StringList
|
|
selectionMulti *gtk.MultiSelection
|
|
selectionSingle *gtk.SingleSelection
|
|
|
|
headerbarStandalone *albumHeaderbar
|
|
playlistStack *gtk.Stack
|
|
panelNormal *gtk.Box
|
|
panelStandalone *gtk.Box
|
|
standaloneStack *gtk.Stack
|
|
standaloneSpinner *gtk.Spinner
|
|
standaloneScroll *gtk.ScrolledWindow
|
|
standaloneImage *gtk.Image
|
|
standalonePlayButton *gtk.Button
|
|
standaloneRemoveButton *gtk.Button
|
|
|
|
selectionRemoveButton *gtk.Button
|
|
selectionCancelButton *gtk.Button
|
|
|
|
coverLoader *CoverLoader
|
|
playlist []*common.PlaylistAlbum
|
|
albums map[string]*common.PlaylistAlbum
|
|
pixbufs map[string]*gdkpixbuf.Pixbuf
|
|
|
|
standaloneAlbum *common.PlaylistAlbum
|
|
standalonePixbuf *gdkpixbuf.Pixbuf
|
|
standaloneCallback func(bool)
|
|
}
|
|
|
|
func newPlaylistPanel() *playlistPanel {
|
|
panel := playlistPanel{
|
|
albums: make(map[string]*common.PlaylistAlbum),
|
|
pixbufs: make(map[string]*gdkpixbuf.Pixbuf),
|
|
}
|
|
panel.loadWidgets()
|
|
panel.connectSignals()
|
|
|
|
return &panel
|
|
}
|
|
|
|
func (p *playlistPanel) loadWidgets() {
|
|
builder := gtk.NewBuilderFromString(data.PlaylistPanelXML)
|
|
|
|
p.toolbar = builder.GetObject("toolbar").Cast().(*gtk.Box)
|
|
p.selectButton = builder.GetObject("select_button").Cast().(*gtk.ToggleButton)
|
|
p.actionbarRevealer = builder.GetObject("actionbar_revealer").Cast().(*gtk.Revealer)
|
|
p.clearButton = builder.GetObject("playlist_clear_button").Cast().(*gtk.Button)
|
|
|
|
p.Bin = builder.GetObject("McgPlaylistPanel").Cast().(*adw.Bin)
|
|
p.playlistGrid = builder.GetObject("playlist_grid").Cast().(*gtk.GridView)
|
|
p.playlistModel = gtk.NewStringList(nil)
|
|
p.selectionMulti = gtk.NewMultiSelection(p.playlistModel)
|
|
p.selectionSingle = gtk.NewSingleSelection(p.playlistModel)
|
|
p.playlistGrid.SetModel(p.selectionSingle)
|
|
|
|
p.gridFactory = newPictureItemFactory[string](p.itemToModelID, p.modelIDToValue)
|
|
p.playlistGrid.SetFactory(p.gridFactory.ListItemFactory())
|
|
|
|
// Headerbar
|
|
p.headerbarStandalone = newAlbumHeaderbar()
|
|
p.playlistStack = builder.GetObject("playlist_stack").Cast().(*gtk.Stack)
|
|
p.panelNormal = builder.GetObject("panel_normal").Cast().(*gtk.Box)
|
|
p.panelStandalone = builder.GetObject("panel_standalone").Cast().(*gtk.Box)
|
|
|
|
p.standaloneStack = builder.GetObject("standalone_stack").Cast().(*gtk.Stack)
|
|
p.standaloneSpinner = builder.GetObject("standalone_spinner").Cast().(*gtk.Spinner)
|
|
p.standaloneScroll = builder.GetObject("standalone_scroll").Cast().(*gtk.ScrolledWindow)
|
|
p.standaloneImage = builder.GetObject("standalone_image").Cast().(*gtk.Image)
|
|
p.standalonePlayButton = builder.GetObject("standalone_play_button").Cast().(*gtk.Button)
|
|
p.standaloneRemoveButton = builder.GetObject("standalone_remove_button").Cast().(*gtk.Button)
|
|
|
|
// Selection
|
|
p.selectionRemoveButton = builder.GetObject("selection_remove_button").Cast().(*gtk.Button)
|
|
p.selectionCancelButton = builder.GetObject("selection_cancel_button").Cast().(*gtk.Button)
|
|
}
|
|
|
|
func (p *playlistPanel) itemToModelID(item *glib.Object) string {
|
|
return item.Cast().(*gtk.StringObject).String()
|
|
}
|
|
|
|
func (p *playlistPanel) modelIDToValue(itemID string) (tooltip string, pixbuf *gdkpixbuf.Pixbuf) {
|
|
if album, ok := p.albums[itemID]; ok {
|
|
tooltip = album.Tooltip()
|
|
}
|
|
pixbuf = p.pixbufs[itemID]
|
|
|
|
return
|
|
}
|
|
|
|
func (p *playlistPanel) connectSignals() {
|
|
p.selectButton.ConnectToggled(p.onSelectToggled)
|
|
p.clearButton.ConnectClicked(p.onClearClicked)
|
|
|
|
p.playlistGrid.ConnectActivate(p.onGridClicked)
|
|
|
|
p.headerbarStandalone.ConnectClose(p.onHeaderbarStandaloneClose)
|
|
p.standalonePlayButton.ConnectClicked(p.onStandalonePlayClicked)
|
|
p.standaloneRemoveButton.ConnectClicked(p.onStandaloneRemoveClicked)
|
|
|
|
p.selectionRemoveButton.ConnectClicked(p.onSelectionRemove)
|
|
p.selectionCancelButton.ConnectClicked(p.onSelectionCancel)
|
|
}
|
|
|
|
func (p *playlistPanel) ConnectStandalone(callback func(bool)) {
|
|
p.standaloneCallback = callback
|
|
}
|
|
|
|
func (p *playlistPanel) onSelectToggled() {
|
|
if p.selectButton.Active() {
|
|
p.actionbarRevealer.SetRevealChild(true)
|
|
p.playlistGrid.SetModel(p.selectionMulti)
|
|
p.playlistGrid.SetSingleClickActivate(false)
|
|
p.playlistGrid.StyleContext().AddClass("selection")
|
|
} else {
|
|
p.actionbarRevealer.SetRevealChild(false)
|
|
p.playlistGrid.SetModel(p.selectionSingle)
|
|
p.playlistGrid.SetSingleClickActivate(true)
|
|
p.playlistGrid.StyleContext().AddClass("selection")
|
|
}
|
|
}
|
|
|
|
func (p *playlistPanel) onClearClicked() {
|
|
client.Instance().ClearPlaylist()
|
|
}
|
|
|
|
func (p *playlistPanel) onGridClicked(position uint) {
|
|
// Get selected album
|
|
itemObj := p.playlistModel.Item(position)
|
|
if itemObj == nil {
|
|
return
|
|
}
|
|
strObj := itemObj.Cast().(*gtk.StringObject)
|
|
id := strObj.String()
|
|
album, found := p.albums[id]
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
// Show standalone album
|
|
if p.playlistGrid.Model().Native() == p.selectionSingle.Native() {
|
|
p.standaloneAlbum = album
|
|
p.headerbarStandalone.SetAlbum(album.Album)
|
|
p.standaloneStack.SetVisibleChild(p.standaloneSpinner)
|
|
p.standaloneSpinner.Start()
|
|
p.openStandalone()
|
|
client.Instance().LoadAlbumart(album.Album)
|
|
}
|
|
}
|
|
|
|
func (p *playlistPanel) onHeaderbarStandaloneClose() {
|
|
p.closeStandalone()
|
|
}
|
|
|
|
func (p *playlistPanel) openStandalone() {
|
|
p.playlistStack.SetVisibleChild(p.panelStandalone)
|
|
if p.standaloneCallback != nil {
|
|
p.standaloneCallback(true)
|
|
}
|
|
}
|
|
|
|
func (p *playlistPanel) closeStandalone() {
|
|
p.playlistStack.SetVisibleChild(p.panelNormal)
|
|
if p.standaloneCallback != nil {
|
|
p.standaloneCallback(false)
|
|
}
|
|
}
|
|
|
|
func (p *playlistPanel) setCoverLoader(loader *CoverLoader) {
|
|
p.coverLoader = loader
|
|
}
|
|
|
|
func (p *playlistPanel) SetAlbumart(album *common.Album, albumart []byte) {
|
|
if p.standaloneAlbum == nil || album != p.standaloneAlbum.Album {
|
|
return
|
|
}
|
|
|
|
p.standalonePixbuf = nil
|
|
if albumart == nil || len(albumart) == 0 {
|
|
return
|
|
}
|
|
|
|
loader, loaderErr := loadPixbuf(albumart)
|
|
if loaderErr != nil {
|
|
log.Println("failed to load albumart for ", album.Title(), ":", loaderErr)
|
|
return
|
|
}
|
|
|
|
p.standalonePixbuf = loader.Pixbuf()
|
|
glib.IdleAdd(p.showStandaloneImage)
|
|
}
|
|
|
|
func (p *playlistPanel) showStandaloneImage() {
|
|
p.resizeStandaloneImage()
|
|
p.standaloneStack.SetVisibleChild(p.standaloneScroll)
|
|
p.standaloneSpinner.Stop()
|
|
}
|
|
|
|
func (p *playlistPanel) resizeStandaloneImage() {
|
|
newWidth, newHeight := p.getStandaloneSize()
|
|
if p.standalonePixbuf == nil {
|
|
p.standaloneImage.SetPixelSize(min(newWidth, newHeight))
|
|
return
|
|
}
|
|
|
|
width, height := p.calculateCoverSize(newWidth, newHeight)
|
|
if width <= 0 || height <= 0 {
|
|
return
|
|
}
|
|
|
|
scaledPixbuf := p.standalonePixbuf.ScaleSimple(width, height, gdkpixbuf.InterpHyper)
|
|
p.standaloneImage.SetFromPaintable(gdk.NewTextureForPixbuf(scaledPixbuf))
|
|
p.standaloneImage.SetPixelSize(min(width, height))
|
|
}
|
|
|
|
func (p *playlistPanel) getStandaloneSize() (int, int) {
|
|
return p.standaloneStack.Size(gtk.OrientationHorizontal), p.standaloneStack.Size(gtk.OrientationVertical)
|
|
}
|
|
|
|
func (p *playlistPanel) calculateCoverSize(currentWidth, currentHeight int) (width, height int) {
|
|
ratioWidth := float64(currentWidth) / float64(p.standalonePixbuf.Width())
|
|
ratioHeight := float64(currentHeight) / float64(p.standalonePixbuf.Height())
|
|
ratio := min(min(ratioWidth, ratioHeight), 1)
|
|
|
|
width = int(math.Floor(float64(p.standalonePixbuf.Width()) * ratio))
|
|
height = int(math.Floor(float64(p.standalonePixbuf.Height()) * ratio))
|
|
|
|
return
|
|
}
|
|
|
|
func (p *playlistPanel) onStandalonePlayClicked() {
|
|
client.Instance().PlayQueuedAlbum(p.standaloneAlbum)
|
|
p.closeStandalone()
|
|
}
|
|
|
|
func (p *playlistPanel) onStandaloneRemoveClicked() {
|
|
client.Instance().UnqueueAlbum(p.standaloneAlbum)
|
|
p.closeStandalone()
|
|
}
|
|
|
|
func (p *playlistPanel) onSelectionRemove() {
|
|
albums := p.getSelectedAlbums()
|
|
if len(albums) > 0 {
|
|
client.Instance().UnqueueAlbums(albums)
|
|
}
|
|
|
|
p.selectButton.SetActive(false)
|
|
}
|
|
|
|
func (p *playlistPanel) getSelectedAlbums() []*common.PlaylistAlbum {
|
|
albums := []*common.PlaylistAlbum{}
|
|
for i := range p.selectionMulti.NItems() {
|
|
if p.selectionMulti.IsSelected(i) {
|
|
itemObj := p.selectionMulti.Item(i)
|
|
if itemObj != nil {
|
|
itemStr := itemObj.Cast().(*gtk.StringObject)
|
|
albumID := itemStr.String()
|
|
album, found := p.albums[albumID]
|
|
if found {
|
|
albums = append(albums, album)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return albums
|
|
}
|
|
|
|
func (p *playlistPanel) onSelectionCancel() {
|
|
p.selectButton.SetActive(false)
|
|
}
|
|
|
|
func (p *playlistPanel) Toolbar() gtk.Widgetter {
|
|
return p.toolbar
|
|
}
|
|
|
|
func (p *playlistPanel) ItemSizeChanged() {
|
|
p.redraw()
|
|
}
|
|
|
|
func (p *playlistPanel) redraw() {
|
|
if len(p.playlist) > 0 {
|
|
p.SetPlaylist(p.playlist)
|
|
}
|
|
}
|
|
|
|
func (p *playlistPanel) SetPlaylist(playlist []*common.PlaylistAlbum) {
|
|
go p.setPlaylist(playlist)
|
|
}
|
|
|
|
func (p *playlistPanel) setPlaylist(playlist []*common.PlaylistAlbum) {
|
|
p.playlist = playlist
|
|
|
|
for _, album := range playlist {
|
|
p.albums[album.ID()] = album
|
|
if p.coverLoader != nil {
|
|
p.pixbufs[album.ID()] = p.coverLoader.LoadThumbnail(album.Album)
|
|
}
|
|
}
|
|
|
|
p.updateGridModel()
|
|
}
|
|
|
|
func (p *playlistPanel) updateGridModel() {
|
|
p.updateGrid(p.playlist)
|
|
}
|
|
|
|
func (p *playlistPanel) updateGrid(albums []*common.PlaylistAlbum) {
|
|
glib.IdleAdd(func() {
|
|
n := p.playlistModel.NItems()
|
|
if n > 0 {
|
|
p.playlistModel.Splice(0, n, nil)
|
|
}
|
|
|
|
ids := make([]string, 0, len(albums))
|
|
for _, item := range albums {
|
|
ids = append(ids, item.ID())
|
|
}
|
|
|
|
if len(ids) > 0 {
|
|
p.playlistModel.Splice(0, 0, ids)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (p *playlistPanel) SetWidth(width int) {
|
|
p.resizeStandaloneImage()
|
|
}
|
|
|
|
func (p *playlistPanel) ClearPlaylist() {
|
|
client.Instance().ClearPlaylist()
|
|
}
|
|
|
|
func (p *playlistPanel) HeaderbarStandalone() *albumHeaderbar {
|
|
return p.headerbarStandalone
|
|
}
|