package widgets import ( "context" "image" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/ui/layouts" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) var _ fyne.Widget = (*GridViewItem)(nil) var _ fyne.Widget = (*coverImage)(nil) type coverImage struct { widget.BaseWidget Im *ImagePlaceholder playbtn *canvas.Image OnPlay func() OnShowPage func() OnShowContextMenu func(fyne.Position) } func newCoverImage(placeholderResource fyne.Resource) *coverImage { c := &coverImage{} c.Im = NewImagePlaceholder(placeholderResource, 200) c.Im.OnTapped = c.Tapped c.Im.OnTappedSecondary = c.TappedSecondary c.Im.ScaleMode = canvas.ImageScaleFastest c.playbtn = &canvas.Image{FillMode: canvas.ImageFillContain, Resource: res.ResPlaybuttonPng} c.playbtn.SetMinSize(fyne.NewSize(60, 60)) c.playbtn.Hidden = true c.ExtendBaseWidget(c) return c } func (c *coverImage) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer( container.NewMax(c.Im, container.NewCenter(c.playbtn)), ) } func (c *coverImage) Cursor() desktop.Cursor { return desktop.PointerCursor } func (c *coverImage) Tapped(e *fyne.PointEvent) { if isInside(c.center(), c.playbtn.Size().Height/2, e.Position) { if c.OnPlay != nil { c.OnPlay() } return } if c.OnShowPage != nil { c.OnShowPage() } } func (c *coverImage) TappedSecondary(e *fyne.PointEvent) { if c.OnShowContextMenu != nil { c.OnShowContextMenu(e.AbsolutePosition) } } func (a *coverImage) MouseIn(*desktop.MouseEvent) { a.playbtn.Hidden = false a.Refresh() } func (a *coverImage) MouseOut() { a.playbtn.Hidden = true a.Refresh() } func (a *coverImage) MouseMoved(e *desktop.MouseEvent) { if isInside(a.center(), a.playbtn.MinSize().Height/2, e.Position) { a.playbtn.SetMinSize(fyne.NewSize(65, 65)) } else { a.playbtn.SetMinSize(fyne.NewSize(60, 60)) } a.Refresh() } func (a *coverImage) center() fyne.Position { return fyne.NewPos(a.Size().Width/2, a.Size().Height/2) } func (a *coverImage) SetImage(im image.Image) { a.Im.SetImage(im, true) } func isInside(origin fyne.Position, radius float32, point fyne.Position) bool { x, y := (point.X - origin.X), (point.Y - origin.Y) return x*x+y*y <= radius*radius } type GridViewItemModel struct { Name string ID string CoverArtID string Secondary string SecondaryID string } type GridViewItem struct { widget.BaseWidget itemID string secondaryID string primaryText *CustomHyperlink secondaryText *CustomHyperlink menu *widget.PopUpMenu container *fyne.Container // updated by GridView Cover *coverImage // these fields are used by GridView to track async update tasks PrevID string ImgLoadCancel context.CancelFunc OnPlay func(shuffle bool) OnAddToQueue func() OnAddToPlaylist func() OnDownload func() OnShowItemPage func() OnShowSecondaryPage func() } func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { g := &GridViewItem{ primaryText: NewCustomHyperlink(), secondaryText: NewCustomHyperlink(), Cover: newCoverImage(placeholderResource), } g.primaryText.SetTextStyle(fyne.TextStyle{Bold: true}) g.ExtendBaseWidget(g) g.Cover.OnPlay = func() { g.onPlay(false) } g.Cover.OnShowContextMenu = g.showContextMenu showItemFn := func() { if g.OnShowItemPage != nil { g.OnShowItemPage() } } g.Cover.OnShowPage = showItemFn g.primaryText.OnTapped = showItemFn g.secondaryText.OnTapped = func() { if g.OnShowSecondaryPage != nil { g.OnShowSecondaryPage() } } g.createContainer() return g } func (g *GridViewItem) createContainer() { info := container.New(&layouts.VboxCustomPadding{ExtraPad: -16}, g.primaryText, g.secondaryText) c := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, g.Cover, info) pad := &layouts.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10} g.container = container.New(pad, c) } func (g *GridViewItem) showContextMenu(pos fyne.Position) { if g.menu == nil { g.menu = widget.NewPopUpMenu(fyne.NewMenu("", fyne.NewMenuItem("Play", func() { g.onPlay(false) }), fyne.NewMenuItem("Shuffle", func() { g.onPlay(true) }), fyne.NewMenuItem("Add to queue", g.onAddToQueue), fyne.NewMenuItem("Add to playlist...", g.onAddToPlaylist), fyne.NewMenuItem("Download...", g.onDownload)), fyne.CurrentApp().Driver().CanvasForObject(g)) } g.menu.ShowAtPosition(pos) } func (g *GridViewItem) Update(model GridViewItemModel) { g.itemID = model.ID g.secondaryID = model.SecondaryID g.primaryText.SetText(model.Name) g.secondaryText.SetText(model.Secondary) g.secondaryText.Disabled = model.SecondaryID == "" g.secondaryText.Refresh() g.Cover.playbtn.Hidden = true } func (g *GridViewItem) Refresh() { g.BaseWidget.Refresh() } func (g *GridViewItem) ItemID() string { return g.itemID } func (g *GridViewItem) SecondaryID() string { return g.secondaryID } func (g *GridViewItem) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(g.container) } func (g *GridViewItem) onPlay(shuffle bool) { if g.OnPlay != nil { g.OnPlay(shuffle) } } func (g *GridViewItem) onAddToQueue() { if g.OnAddToQueue != nil { g.OnAddToQueue() } } func (g *GridViewItem) onAddToPlaylist() { if g.OnAddToPlaylist != nil { g.OnAddToPlaylist() } } func (g *GridViewItem) onDownload() { if g.OnDownload != nil { g.OnDownload() } }