diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 58387eb..652f0f6 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -78,6 +78,11 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr bp.NowPlaying.OnTrackNameTapped = func() { contr.NavigateTo(controller.NowPlayingRoute(pm.NowPlaying().Metadata().ID)) } + bp.NowPlaying.OnShare = func() { + if tr, ok := pm.NowPlaying().(*mediaprovider.Track); ok { + contr.ShowShareDialog(tr.ID) + } + } bp.Controls = widgets.NewPlayerControls() bp.Controls.OnPlayPause(func() { pm.PlayPause() diff --git a/ui/widgets/nowplayingcard.go b/ui/widgets/nowplayingcard.go index 9cf2b2b..6118959 100644 --- a/ui/widgets/nowplayingcard.go +++ b/ui/widgets/nowplayingcard.go @@ -22,7 +22,7 @@ type NowPlayingCard struct { DisableRating bool - trackName *widget.Hyperlink + trackName *OptionHyperlink artistName *MultiHyperlink albumName *widget.Hyperlink cover *ImagePlaceholder @@ -36,28 +36,28 @@ type NowPlayingCard struct { OnSetRating func(rating int) OnSetFavorite func(favorite bool) OnAddToPlaylist func() + OnShare func() } func NewNowPlayingCard() *NowPlayingCard { n := &NowPlayingCard{ - trackName: widget.NewHyperlink("", nil), + trackName: NewOptionHyperlink(), artistName: NewMultiHyperlink(), albumName: widget.NewHyperlink("", nil), } n.ExtendBaseWidget(n) n.cover = NewImagePlaceholder(myTheme.TracksIcon, 85) n.cover.OnTapped = n.onShowCoverImage - n.cover.OnTappedSecondary = n.showMenu n.cover.ScaleMode = canvas.ImageScaleFastest n.cover.Hidden = true n.trackName.Hidden = true n.albumName.Hidden = true n.albumName.Truncation = fyne.TextTruncateEllipsis - n.trackName.Truncation = fyne.TextTruncateEllipsis - n.trackName.TextStyle.Bold = true + n.trackName.SetTextStyle(fyne.TextStyle{Bold: true}) + n.trackName.OnShowMenu = n.showMenu n.albumName.OnTapped = n.onAlbumNameTapped n.artistName.OnTapped = n.onArtistNameTapped - n.trackName.OnTapped = n.onTrackNameTapped + n.trackName.SetOnTapped(n.onTrackNameTapped) return n } @@ -109,6 +109,12 @@ func (n *NowPlayingCard) onAddToPlaylist() { } } +func (n *NowPlayingCard) onShare() { + if n.OnShare != nil { + n.OnShare() + } +} + func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer { c := container.New(&layout.CustomPaddedLayout{LeftPadding: -4}, container.NewBorder(nil, nil, n.cover, nil, @@ -137,7 +143,8 @@ func (n *NowPlayingCard) Update(track mediaprovider.MediaItem) { n.cover.PlaceholderIcon = myTheme.RadioIcon } } - n.trackName.Hidden = n.trackName.Text == "" + n.trackName.Hidden = n.trackName.Text() == "" + n.trackName.SetMenuBtnEnabled(n.cover.PlaceholderIcon != myTheme.RadioIcon) n.artistName.Hidden = len(n.artistName.Segments) == 0 n.albumName.Hidden = n.albumName.Text == "" n.Refresh() @@ -147,7 +154,7 @@ func (n *NowPlayingCard) SetImage(cover image.Image) { n.cover.SetImage(cover, true) } -func (n *NowPlayingCard) showMenu(e *fyne.PointEvent) { +func (n *NowPlayingCard) showMenu(btnPos fyne.Position) { if n.menu == nil { n.ratingMenu = util.NewRatingSubmenu(n.onSetRating) favorite := fyne.NewMenuItem("Set favorite", func() { n.onSetFavorite(true) }) @@ -156,10 +163,15 @@ func (n *NowPlayingCard) showMenu(e *fyne.PointEvent) { unfavorite.Icon = myTheme.NotFavoriteIcon playlist := fyne.NewMenuItem("Add to playlist...", func() { n.onAddToPlaylist() }) playlist.Icon = myTheme.PlaylistIcon + share := fyne.NewMenuItem("Share...", func() { n.onShare() }) + share.Icon = myTheme.ShareIcon - m := fyne.NewMenu("", favorite, unfavorite, n.ratingMenu, playlist) + m := fyne.NewMenu("", favorite, unfavorite, n.ratingMenu, playlist, share) n.menu = widget.NewPopUpMenu(m, fyne.CurrentApp().Driver().CanvasForObject(n)) } + menuSize := n.menu.MinSize() n.ratingMenu.Disabled = n.DisableRating - n.menu.ShowAtPosition(e.AbsolutePosition) + btnPos.Y -= (menuSize.Height + theme.Padding()*3) + btnPos.X -= menuSize.Width / 2 + n.menu.ShowAtPosition(btnPos) } diff --git a/ui/widgets/optionhyperlink.go b/ui/widgets/optionhyperlink.go new file mode 100644 index 0000000..1fdda1a --- /dev/null +++ b/ui/widgets/optionhyperlink.go @@ -0,0 +1,106 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// OptionHyperlink is a widget that displays a "vertical dots"/"more" button +// to the right of the text, which can show a menu. +type OptionHyperlink struct { + widget.BaseWidget + h *widget.Hyperlink + b *IconButton + + OnShowMenu func(btnPos fyne.Position) + + layout optionHyperlinkLayout +} + +func NewOptionHyperlink() *OptionHyperlink { + o := &OptionHyperlink{ + h: widget.NewHyperlink("", nil), + } + o.h.Truncation = fyne.TextTruncateEllipsis + o.b = NewIconButton(theme.MoreVerticalIcon(), func() { + if o.OnShowMenu != nil { + o.OnShowMenu(fyne.CurrentApp().Driver().AbsolutePositionForObject(o.b)) + } + }) + o.b.IconSize = IconButtonSizeSmaller + o.ExtendBaseWidget(o) + return o +} + +func (o *OptionHyperlink) SetText(text string) { + o.h.SetText(text) + o.updatePreferredWidth() + o.BaseWidget.Refresh() +} + +func (o *OptionHyperlink) Text() string { + return o.h.Text +} + +func (o *OptionHyperlink) SetTextStyle(style fyne.TextStyle) { + o.h.TextStyle = style + o.updatePreferredWidth() + o.BaseWidget.Refresh() +} + +func (o *OptionHyperlink) SetOnTapped(f func()) { + o.h.OnTapped = f +} + +func (o *OptionHyperlink) SetMenuBtnEnabled(enabled bool) { + o.b.Hidden = !enabled +} + +func (o *OptionHyperlink) updatePreferredWidth() { + var h widget.Hyperlink + h.Text = o.Text() + h.TextStyle = o.h.TextStyle + o.layout.preferredWidth = h.MinSize().Width + theme.Padding() +} + +func (o *OptionHyperlink) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer( + container.New(&o.layout, o.h, o.b), + ) +} + +var _ fyne.Layout = (*optionHyperlinkLayout)(nil) + +type optionHyperlinkLayout struct { + preferredWidth float32 +} + +func (o *optionHyperlinkLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { + var minSize fyne.Size + for _, obj := range objects { + if !obj.Visible() { + continue + } + ms := obj.MinSize() + minSize.Height = fyne.Max(ms.Height, minSize.Height) + minSize.Width += ms.Width + } + return minSize +} + +// only supports the two objects +func (o *optionHyperlinkLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { + if !objects[1].Visible() { + objects[0].Move(fyne.NewPos(0, 0)) + objects[0].Resize(size) + return + } + btnWidth := objects[1].MinSize().Width + hypWidth := fyne.Min(o.preferredWidth, size.Width-btnWidth) + objects[0].Resize(fyne.NewSize(hypWidth, size.Height)) + objects[0].Move(fyne.NewPos(0, 0)) + objects[1].Resize(fyne.NewSize(btnWidth, size.Height)) + objects[1].Move(fyne.NewPos(hypWidth-theme.Padding()*2, 0)) +}