From c3143dbbbec3071b62423c975374f1fcea1f37b5 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 20 Oct 2023 09:19:49 -0700 Subject: [PATCH] WIP - multi hyperlink --- ui/widgets/disableablehyperlink.go | 73 ----------------------- ui/widgets/gridviewitem.go | 10 ++-- ui/widgets/multihyperlink.go | 96 ++++++++++++++++++++++++++++++ ui/widgets/nowplayingcard.go | 9 ++- ui/widgets/tracklist.go | 15 ++--- 5 files changed, 109 insertions(+), 94 deletions(-) delete mode 100644 ui/widgets/disableablehyperlink.go create mode 100644 ui/widgets/multihyperlink.go diff --git a/ui/widgets/disableablehyperlink.go b/ui/widgets/disableablehyperlink.go deleted file mode 100644 index fa3845b..0000000 --- a/ui/widgets/disableablehyperlink.go +++ /dev/null @@ -1,73 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" -) - -type DisableableHyperlink struct { - widget.BaseWidget - h *widget.Hyperlink - l *widget.Label - - OnTapped func() - NoTruncate bool - Disabled bool - - lastDisabled bool - container *fyne.Container -} - -func NewDisableableHyperlink() *DisableableHyperlink { - c := &DisableableHyperlink{ - h: widget.NewHyperlink("", nil), - l: widget.NewLabel(""), - container: container.NewMax(), - } - c.h.OnTapped = func() { - if c.OnTapped != nil { - c.OnTapped() - } - } - c.ExtendBaseWidget(c) - c.updateContainer(c.Disabled) - return c -} - -func (c *DisableableHyperlink) SetText(text string) { - lastWrapping := c.l.Wrapping - c.l.Wrapping = fyne.TextWrapOff - c.l.SetText(text) - c.h.SetText(text) - c.l.Wrapping = lastWrapping - c.Refresh() -} - -func (c *DisableableHyperlink) Refresh() { - if c.NoTruncate { - c.h.Wrapping = fyne.TextWrapOff - c.l.Wrapping = fyne.TextWrapOff - } else { - c.h.Wrapping = fyne.TextTruncate - c.l.Wrapping = fyne.TextTruncate - } - if c.lastDisabled != c.Disabled { - c.updateContainer(c.Disabled) - c.lastDisabled = c.Disabled - } - c.container.Refresh() -} - -func (c *DisableableHyperlink) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(c.container) -} - -func (c *DisableableHyperlink) updateContainer(linkDisabled bool) { - c.container.RemoveAll() - if linkDisabled { - c.container.Add(c.l) - } else { - c.container.Add(c.h) - } -} diff --git a/ui/widgets/gridviewitem.go b/ui/widgets/gridviewitem.go index d245497..ae062c7 100644 --- a/ui/widgets/gridviewitem.go +++ b/ui/widgets/gridviewitem.go @@ -135,7 +135,7 @@ type GridViewItem struct { itemID string secondaryID string primaryText *widget.Hyperlink - secondaryText *DisableableHyperlink + secondaryText *MultiHyperlink container *fyne.Container // updated by GridView @@ -151,7 +151,7 @@ type GridViewItem struct { func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { g := &GridViewItem{ primaryText: widget.NewHyperlink("", nil), - secondaryText: NewDisableableHyperlink(), + secondaryText: NewMultiHyperlink(), Cover: newCoverImage(placeholderResource), } g.primaryText.TextStyle.Bold = true @@ -174,7 +174,7 @@ func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { } g.Cover.OnShowPage = showItemFn g.primaryText.OnTapped = showItemFn - g.secondaryText.OnTapped = func() { + g.secondaryText.OnTapped = func(_ string) { // TODO if g.OnShowSecondaryPage != nil { g.OnShowSecondaryPage() } @@ -199,8 +199,8 @@ func (g *GridViewItem) Update(model GridViewItemModel) { g.itemID = model.ID g.secondaryID = model.SecondaryID g.primaryText.SetText(model.Name) - g.secondaryText.Disabled = model.SecondaryID == "" - g.secondaryText.SetText(model.Secondary) + g.secondaryText.Segments = []MultiHyperlinkSegment{{Text: model.Secondary, LinkID: model.SecondaryID}} + g.secondaryText.Refresh() g.Cover.ResetPlayButton() } diff --git a/ui/widgets/multihyperlink.go b/ui/widgets/multihyperlink.go new file mode 100644 index 0000000..2bbd4f6 --- /dev/null +++ b/ui/widgets/multihyperlink.go @@ -0,0 +1,96 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +type MultiHyperlink struct { + widget.BaseWidget + + Segments []MultiHyperlinkSegment + OnTapped func(string) + + provider *widget.RichText +} + +type MultiHyperlinkSegment struct { + Text string + LinkID string +} + +func NewMultiHyperlink() *MultiHyperlink { + c := &MultiHyperlink{ + provider: widget.NewRichText(), + } + c.ExtendBaseWidget(c) + c.provider.Wrapping = fyne.TextTruncate + return c +} + +func (c *MultiHyperlink) syncSegments() { + l := len(c.provider.Segments) + for i, seg := range c.Segments { + appendingSegments := 2*i >= l // true if we need to extend the RichText provider with new segments + var rtSeg widget.RichTextSegment + if !appendingSegments { + rtSeg = c.provider.Segments[2*i] + } else if i > 0 { + // append new separator segment + c.provider.Segments = append(c.provider.Segments, c.newSeparatorSegment()) + } + if seg.LinkID == "" { + rtSeg = c.updateOrReplaceTextSegment(rtSeg, seg.Text) + } else { + rtSeg = c.updateOrReplaceHyperlinkSegment(rtSeg, seg.Text, seg.LinkID) + } + if appendingSegments { + c.provider.Segments = append(c.provider.Segments, rtSeg) + } else { + c.provider.Segments[2*i] = rtSeg + } + } + // discard extra segments if shortening the multihyperlink + for i := 2 * len(c.Segments); i < l; i++ { + c.provider.Segments[i] = nil + } + c.provider.Segments = c.provider.Segments[:2*len(c.Segments)-1] +} + +func (c *MultiHyperlink) newSeparatorSegment() widget.RichTextSegment { + return &widget.TextSegment{Text: ", ", Style: widget.RichTextStyle{Inline: true}} +} + +func (c *MultiHyperlink) updateOrReplaceTextSegment(seg widget.RichTextSegment, text string) widget.RichTextSegment { + if seg != nil { + if ts, ok := seg.(*widget.TextSegment); ok { + ts.Text = text + return seg + } + } + return &widget.TextSegment{Text: text, Style: widget.RichTextStyle{Inline: true}} +} + +func (c *MultiHyperlink) updateOrReplaceHyperlinkSegment(seg widget.RichTextSegment, text, linkID string) widget.RichTextSegment { + if seg != nil { + if ts, ok := seg.(*widget.HyperlinkSegment); ok { + ts.Text = text + // TODO: OnTapped + return seg + } + } + return &widget.HyperlinkSegment{Text: text} // TODO: OnTapped +} + +func (c *MultiHyperlink) onSegmentTapped(linkID string) { + // TODO +} + +func (c *MultiHyperlink) Refresh() { + c.syncSegments() + c.BaseWidget.Refresh() +} + +func (c *MultiHyperlink) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(c.provider) +} diff --git a/ui/widgets/nowplayingcard.go b/ui/widgets/nowplayingcard.go index 3367607..1af563a 100644 --- a/ui/widgets/nowplayingcard.go +++ b/ui/widgets/nowplayingcard.go @@ -18,7 +18,7 @@ type NowPlayingCard struct { widget.BaseWidget trackName *widget.Hyperlink - artistName *DisableableHyperlink + artistName *MultiHyperlink albumName *widget.Hyperlink cover *TappableImage menu *widget.PopUpMenu @@ -34,7 +34,7 @@ type NowPlayingCard struct { func NewNowPlayingCard() *NowPlayingCard { n := &NowPlayingCard{ trackName: widget.NewHyperlink("", nil), - artistName: NewDisableableHyperlink(), + artistName: NewMultiHyperlink(), albumName: widget.NewHyperlink("", nil), } n.ExtendBaseWidget(n) @@ -88,9 +88,8 @@ func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer { func (n *NowPlayingCard) Update(track, artist string, artistNavigable bool, album string, cover image.Image) { n.trackName.SetText(track) n.trackName.Hidden = track == "" - n.artistName.SetText(artist) + n.artistName.Segments = []MultiHyperlinkSegment{{Text: artist}} n.artistName.Hidden = artist == "" - n.artistName.Disabled = !artistNavigable n.albumName.SetText(album) n.albumName.Hidden = album == "" n.cover.Image.Image = cover @@ -98,7 +97,7 @@ func (n *NowPlayingCard) Update(track, artist string, artistNavigable bool, albu } func (n *NowPlayingCard) OnArtistNameTapped(f func()) { - n.artistName.OnTapped = f + //n.artistName.OnTapped = f } func (n *NowPlayingCard) OnAlbumNameTapped(f func()) { diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 15b791d..c34ea29 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -676,7 +676,7 @@ type TrackRow struct { num *widget.RichText name *widget.RichText - artist *DisableableHyperlink + artist *MultiHyperlink album *widget.Hyperlink dur *widget.RichText year *widget.RichText @@ -697,8 +697,8 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.ExtendBaseWidget(t) t.num = newTrailingAlignRichText() t.name = newTruncatingRichText() - t.artist = NewDisableableHyperlink() - t.artist.OnTapped = func() { tracklist.onArtistTapped(t.artistID) } + t.artist = NewMultiHyperlink() + //t.artist.OnTapped = func() { tracklist.onArtistTapped(t.artistID) } // TODO t.album = widget.NewHyperlink("", nil) t.album.Wrapping = fyne.TextTruncate t.album.OnTapped = func() { tracklist.onAlbumTapped(t.albumID) } @@ -748,8 +748,7 @@ func (t *TrackRow) Update(tm *trackModel, rowNum int) { t.albumID = tr.AlbumID t.name.Segments[0].(*widget.TextSegment).Text = tr.Name - t.artist.SetText(tr.ArtistNames[0]) - t.artist.Disabled = tr.ArtistIDs[0] == "" + t.artist.Segments = []MultiHyperlinkSegment{{Text: tr.ArtistNames[0], LinkID: tr.ArtistIDs[0]}} t.album.SetText(tr.Album) t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) t.year.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.Year) @@ -789,12 +788,6 @@ func (t *TrackRow) Update(tm *trackModel, rowNum int) { if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying { t.isPlaying = isPlaying t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.dur.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.year.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.plays.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.bitrate.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.size.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying - t.path.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying if isPlaying { t.Content.(*fyne.Container).Objects[0] = container.NewCenter(t.playingIcon)