add option to show album years in grid views
This commit is contained in:
+10
-1
@@ -3,6 +3,7 @@ package widgets
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
@@ -51,13 +52,17 @@ type gridViewAlbumIterator struct {
|
||||
func (g gridViewAlbumIterator) NextN(n int) []GridViewItemModel {
|
||||
albums := g.iter.NextN(n)
|
||||
return sharedutil.MapSlice(albums, func(al *mediaprovider.Album) GridViewItemModel {
|
||||
return GridViewItemModel{
|
||||
model := GridViewItemModel{
|
||||
Name: al.Name,
|
||||
ID: al.ID,
|
||||
CoverArtID: al.CoverArtID,
|
||||
Secondary: al.ArtistNames,
|
||||
SecondaryIDs: al.ArtistIDs,
|
||||
}
|
||||
if al.Year > 0 {
|
||||
model.Suffix = strconv.Itoa(al.Year)
|
||||
}
|
||||
return model
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,6 +97,8 @@ func NewGridViewArtistIterator(iter mediaprovider.ArtistIterator) GridViewIterat
|
||||
type GridView struct {
|
||||
widget.BaseWidget
|
||||
|
||||
ShowSuffix bool
|
||||
|
||||
stateMutex sync.RWMutex
|
||||
fetchCancel context.CancelFunc
|
||||
GridViewState
|
||||
@@ -322,9 +329,11 @@ func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewItem) {
|
||||
card.ItemIndex = itemIdx
|
||||
g.itemForIndex[itemIdx] = card
|
||||
card.Cover.Im.PlaceholderIcon = g.Placeholder
|
||||
card.ShowSuffix = g.ShowSuffix
|
||||
if !card.NeedsUpdate(item) && card.ItemIndex == itemIdx {
|
||||
// nothing to do
|
||||
g.stateMutex.Unlock()
|
||||
card.Refresh()
|
||||
return
|
||||
}
|
||||
g.stateMutex.Unlock()
|
||||
|
||||
@@ -5,9 +5,6 @@ import (
|
||||
"image/color"
|
||||
"slices"
|
||||
|
||||
"github.com/dweymouth/supersonic/res"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -15,6 +12,10 @@ import (
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"github.com/dweymouth/supersonic/res"
|
||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*GridViewItem)(nil)
|
||||
@@ -131,15 +132,19 @@ type GridViewItemModel struct {
|
||||
CoverArtID string
|
||||
Secondary []string
|
||||
SecondaryIDs []string
|
||||
Suffix string
|
||||
}
|
||||
|
||||
type GridViewItem struct {
|
||||
widget.BaseWidget
|
||||
|
||||
ShowSuffix bool
|
||||
|
||||
itemID string
|
||||
secondaryIDs []string
|
||||
primaryText *widget.Hyperlink
|
||||
secondaryText *MultiHyperlink
|
||||
suffix string
|
||||
container *fyne.Container
|
||||
focused bool
|
||||
focusRect *canvas.Rectangle
|
||||
@@ -164,8 +169,10 @@ func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem {
|
||||
secondaryText: NewMultiHyperlink(),
|
||||
Cover: newCoverImage(placeholderResource),
|
||||
}
|
||||
g.primaryText.TextStyle.Bold = true
|
||||
g.primaryText.Truncation = fyne.TextTruncateEllipsis
|
||||
g.primaryText.TextStyle.Bold = true
|
||||
g.secondaryText.SizeName = myTheme.SizeNameSubText
|
||||
g.secondaryText.SuffixSizeName = myTheme.SizeNameSuffixText
|
||||
g.ExtendBaseWidget(g)
|
||||
g.Cover.OnPlay = func() {
|
||||
if g.OnPlay != nil {
|
||||
@@ -195,7 +202,7 @@ func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem {
|
||||
}
|
||||
|
||||
func (g *GridViewItem) createContainer() {
|
||||
info := container.New(layout.NewCustomPaddedVBoxLayout(theme.Padding()-16), g.primaryText, g.secondaryText)
|
||||
info := container.New(layout.NewCustomPaddedVBoxLayout(theme.Padding()-17), g.primaryText, g.secondaryText)
|
||||
g.focusRect = canvas.NewRectangle(color.Transparent)
|
||||
g.focusRect.StrokeWidth = 3
|
||||
coverStack := container.NewStack(g.Cover, g.focusRect)
|
||||
@@ -205,7 +212,8 @@ func (g *GridViewItem) createContainer() {
|
||||
}
|
||||
|
||||
func (g *GridViewItem) NeedsUpdate(model GridViewItemModel) bool {
|
||||
return g.itemID != model.ID || !slices.Equal(g.secondaryIDs, model.SecondaryIDs)
|
||||
return g.itemID != model.ID || !slices.Equal(g.secondaryIDs, model.SecondaryIDs) ||
|
||||
g.secondaryText.Suffix != model.Suffix
|
||||
}
|
||||
|
||||
func (g *GridViewItem) Update(model GridViewItemModel) {
|
||||
@@ -213,6 +221,11 @@ func (g *GridViewItem) Update(model GridViewItemModel) {
|
||||
g.secondaryIDs = model.SecondaryIDs
|
||||
g.primaryText.SetText(model.Name)
|
||||
g.secondaryText.BuildSegments(model.Secondary, model.SecondaryIDs)
|
||||
if g.ShowSuffix {
|
||||
g.secondaryText.Suffix = model.Suffix
|
||||
} else {
|
||||
g.secondaryText.Suffix = ""
|
||||
}
|
||||
g.secondaryText.Refresh()
|
||||
g.Cover.ResetPlayButton()
|
||||
if g.focused {
|
||||
@@ -222,9 +235,15 @@ func (g *GridViewItem) Update(model GridViewItemModel) {
|
||||
}
|
||||
|
||||
func (g *GridViewItem) Refresh() {
|
||||
if g.ShowSuffix && g.secondaryText.Suffix == "" && g.suffix != "" {
|
||||
g.secondaryText.Suffix = g.suffix
|
||||
g.secondaryText.Refresh()
|
||||
} else if !g.ShowSuffix && g.secondaryText.Suffix != "" {
|
||||
g.secondaryText.Suffix = ""
|
||||
g.secondaryText.Refresh()
|
||||
}
|
||||
g.focusRect.StrokeColor = util.MakeOpaque(theme.FocusColor())
|
||||
g.focusRect.Hidden = !g.focused
|
||||
g.BaseWidget.Refresh()
|
||||
}
|
||||
|
||||
func (g *GridViewItem) ItemID() string {
|
||||
|
||||
@@ -11,8 +11,16 @@ type MultiHyperlink struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Segments []MultiHyperlinkSegment
|
||||
|
||||
// Suffix string that is appended (with · separator)
|
||||
// only if there is enough room
|
||||
Suffix string
|
||||
|
||||
OnTapped func(string)
|
||||
|
||||
SizeName fyne.ThemeSizeName
|
||||
SuffixSizeName fyne.ThemeSizeName
|
||||
|
||||
minSegWidthCached float32
|
||||
minHeightCached float32
|
||||
separatorWCached float32
|
||||
@@ -20,7 +28,10 @@ type MultiHyperlink struct {
|
||||
// TODO: Once https://github.com/fyne-io/fyne/issues/4336 is resolved,
|
||||
// we can switch to the much cleaner RichText implementation
|
||||
//provider *widget.RichText
|
||||
content *fyne.Container
|
||||
|
||||
objects []fyne.CanvasObject
|
||||
suffixLabel *widget.RichText
|
||||
content *fyne.Container
|
||||
}
|
||||
|
||||
type MultiHyperlinkSegment struct {
|
||||
@@ -52,14 +63,14 @@ func (m *MultiHyperlink) BuildSegments(texts, links []string) {
|
||||
|
||||
func (c *MultiHyperlink) getMinSegWidth() float32 {
|
||||
if c.minSegWidthCached == 0 {
|
||||
c.minSegWidthCached = fyne.MeasureText(", W", theme.TextSize(), fyne.TextStyle{}).Width
|
||||
c.minSegWidthCached = fyne.MeasureText(", W", theme.Size(c.sizeName()), fyne.TextStyle{}).Width
|
||||
}
|
||||
return c.minSegWidthCached
|
||||
}
|
||||
|
||||
func (c *MultiHyperlink) getSeparatorWidth() float32 {
|
||||
if c.separatorWCached == 0 {
|
||||
c.separatorWCached = fyne.MeasureText(",", theme.TextSize(), fyne.TextStyle{}).Width
|
||||
c.separatorWCached = fyne.MeasureText(",", theme.Size(c.sizeName()), fyne.TextStyle{}).Width
|
||||
}
|
||||
return c.separatorWCached
|
||||
}
|
||||
@@ -71,7 +82,7 @@ func (c *MultiHyperlink) layoutObjects() {
|
||||
}
|
||||
x := float32(0)
|
||||
width := c.Size().Width
|
||||
l := len(c.content.Objects)
|
||||
l := len(c.objects)
|
||||
|
||||
var i int // at end of loop should be index of last seg that was laid out for display
|
||||
var seg MultiHyperlinkSegment
|
||||
@@ -87,9 +98,9 @@ func (c *MultiHyperlink) layoutObjects() {
|
||||
appendingSegments := 2*i >= l
|
||||
var obj fyne.CanvasObject
|
||||
if !appendingSegments {
|
||||
obj = c.content.Objects[2*i]
|
||||
obj = c.objects[2*i]
|
||||
} else if i > 0 {
|
||||
c.content.Objects = append(c.content.Objects, c.newSeparatorLabel())
|
||||
c.objects = append(c.objects, c.newSeparatorLabel())
|
||||
}
|
||||
if seg.LinkID == "" {
|
||||
obj = c.updateOrReplaceLabel(obj, seg.Text)
|
||||
@@ -97,14 +108,14 @@ func (c *MultiHyperlink) layoutObjects() {
|
||||
obj = c.updateOrReplaceHyperlink(obj, seg.Text, seg.LinkID)
|
||||
}
|
||||
if appendingSegments {
|
||||
c.content.Objects = append(c.content.Objects, obj)
|
||||
c.objects = append(c.objects, obj)
|
||||
} else {
|
||||
c.content.Objects[2*i] = obj
|
||||
c.objects[2*i] = obj
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
// move and resize separator
|
||||
obj = c.content.Objects[2*i-1]
|
||||
obj = c.objects[2*i-1]
|
||||
ms := obj.MinSize()
|
||||
obj.Resize(ms)
|
||||
obj.Move(fyne.NewPos(x-ms.Width+c.getSeparatorWidth()+1, 0)) // this is really ugly
|
||||
@@ -112,8 +123,8 @@ func (c *MultiHyperlink) layoutObjects() {
|
||||
}
|
||||
// move and resize text object
|
||||
// extra +3 to textW gives it just enough space to not trigger ellipsis truncation
|
||||
textW := fyne.MeasureText(seg.Text, theme.TextSize(), fyne.TextStyle{}).Width + theme.Padding()*2 + theme.InnerPadding() + 3
|
||||
obj = c.content.Objects[2*i]
|
||||
textW := fyne.MeasureText(seg.Text, theme.Size(c.sizeName()), fyne.TextStyle{}).Width + theme.Padding()*2 + theme.InnerPadding() + 3
|
||||
obj = c.objects[2*i]
|
||||
ms := obj.MinSize()
|
||||
w := fyne.Min(width-x, textW)
|
||||
obj.Resize(fyne.NewSize(w, ms.Height))
|
||||
@@ -122,17 +133,39 @@ func (c *MultiHyperlink) layoutObjects() {
|
||||
}
|
||||
|
||||
i += 1
|
||||
c.content.Objects = c.content.Objects[:2*i-1]
|
||||
c.content.Objects = c.objects[:2*i-1]
|
||||
if i == len(c.Segments) && c.Suffix != "" {
|
||||
if c.suffixLabel == nil {
|
||||
c.suffixLabel = widget.NewRichTextWithText("· " + c.Suffix)
|
||||
} else {
|
||||
c.suffixLabel.Segments[0].(*widget.TextSegment).Text = "· " + c.Suffix
|
||||
}
|
||||
sizeName := c.sizeName()
|
||||
if c.SuffixSizeName != "" {
|
||||
sizeName = c.SuffixSizeName
|
||||
}
|
||||
// TODO: the magic numbers to get exact positioning here are gross
|
||||
c.suffixLabel.Segments[0].(*widget.TextSegment).Style.SizeName = sizeName
|
||||
innerPad2 := theme.InnerPadding() * 2
|
||||
if x+c.suffixLabel.MinSize().Width-innerPad2*1.3 < width {
|
||||
y := theme.Size(c.sizeName()) - theme.Size(sizeName)
|
||||
c.suffixLabel.Move(fyne.NewPos(x-innerPad2+1, y))
|
||||
c.content.Objects = append(c.content.Objects, c.suffixLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string) fyne.CanvasObject {
|
||||
if obj != nil {
|
||||
if label, ok := obj.(*widget.Label); ok {
|
||||
label.Text = text
|
||||
if label, ok := obj.(*widget.RichText); ok {
|
||||
ts := label.Segments[0].(*widget.TextSegment)
|
||||
ts.Text = text
|
||||
ts.Style.SizeName = c.sizeName()
|
||||
return label
|
||||
}
|
||||
}
|
||||
l := widget.NewLabel(text)
|
||||
l := widget.NewRichTextWithText(text)
|
||||
l.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName()
|
||||
l.Truncation = fyne.TextTruncateEllipsis
|
||||
return l
|
||||
}
|
||||
@@ -141,22 +174,34 @@ func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text, l
|
||||
if obj != nil {
|
||||
if l, ok := obj.(*widget.Hyperlink); ok {
|
||||
l.Text = text
|
||||
l.SizeName = c.sizeName()
|
||||
l.OnTapped = func() { c.onSegmentTapped(link) }
|
||||
return l
|
||||
}
|
||||
}
|
||||
l := widget.NewHyperlink(text, nil)
|
||||
l.SizeName = c.sizeName()
|
||||
l.Truncation = fyne.TextTruncateEllipsis
|
||||
l.OnTapped = func() { c.onSegmentTapped(link) }
|
||||
return l
|
||||
}
|
||||
|
||||
func (c *MultiHyperlink) newSeparatorLabel() *widget.Label {
|
||||
return widget.NewLabel(", ")
|
||||
func (c *MultiHyperlink) newSeparatorLabel() *widget.RichText {
|
||||
rt := widget.NewRichTextWithText(", ")
|
||||
rt.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName()
|
||||
return rt
|
||||
}
|
||||
|
||||
func (c *MultiHyperlink) sizeName() fyne.ThemeSizeName {
|
||||
if c.SizeName == "" {
|
||||
return theme.SizeNameText
|
||||
}
|
||||
return c.SizeName
|
||||
}
|
||||
|
||||
/***
|
||||
* RichText implementation
|
||||
* TODO: add support for SizeName, suffix
|
||||
|
||||
func (c *MultiHyperlink) syncSegments() {
|
||||
l := len(c.provider.Segments)
|
||||
|
||||
Reference in New Issue
Block a user