diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index b7f6e0b..96cf0ac 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -186,8 +186,8 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { container.NewVBox( container.New(&layouts.VboxCustomPadding{ExtraPad: -12}, a.artistLabel, a.genreLabel, a.miscLabel), container.NewVBox( - container.NewHBox(widgets.NewHSpace(2), playButton, shuffleBtn), - container.NewHBox(widgets.NewHSpace(2), a.toggleFavButton), + container.NewHBox(util.NewHSpace(2), playButton, shuffleBtn), + container.NewHBox(util.NewHSpace(2), a.toggleFavButton), ), ), ), diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index a066947..bebec6f 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -4,6 +4,7 @@ import ( "supersonic/backend" "supersonic/sharedutil" "supersonic/ui/controller" + "supersonic/ui/util" "supersonic/ui/widgets" "fyne.io/fyne/v2" @@ -92,7 +93,7 @@ func (a *AlbumsPage) createContainer(searchgrid bool) { g = a.searchGrid } a.container = container.NewBorder( - container.NewHBox(widgets.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), searchVbox, widgets.NewHSpace(12)), + container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), searchVbox, util.NewHSpace(12)), nil, nil, nil, diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index f4c6624..95d230f 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -73,7 +73,7 @@ func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.P viewToggle.OnChanged = a.onViewChange //line := canvas.NewLine(theme.TextColor()) viewToggleRow := container.NewBorder(nil, nil, - container.NewHBox(&widgets.HSpace{Width: 5}, viewToggle), nil, + container.NewHBox(util.NewHSpace(5), viewToggle), nil, layout.NewSpacer(), ) a.container = container.NewBorder( @@ -329,7 +329,7 @@ func (a *ArtistPageHeader) createContainer() { container.NewVBox( container.New(&layouts.VboxCustomPadding{ExtraPad: -10}, a.titleDisp, a.biographyDisp, a.similarArtists), - container.NewHBox(widgets.NewHSpace(2), a.favoriteBtn, a.playBtn, a.playRadioBtn))) + container.NewHBox(util.NewHSpace(2), a.favoriteBtn, a.playBtn, a.playRadioBtn))) } func (a *ArtistPageHeader) CreateRenderer() fyne.WidgetRenderer { diff --git a/ui/browsing/artistsgenrespage.go b/ui/browsing/artistsgenrespage.go index 436554d..f325f04 100644 --- a/ui/browsing/artistsgenrespage.go +++ b/ui/browsing/artistsgenrespage.go @@ -25,7 +25,7 @@ type ArtistsGenresPage struct { sm *backend.ServerManager titleDisp *widget.RichText container *fyne.Container - list *widgets.ArtistGenrePlaylist + list *widgets.ArtistGenreList } func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager) *ArtistsGenresPage { @@ -41,7 +41,7 @@ func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b } a.ExtendBaseWidget(a) a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText - a.list = widgets.NewArtistGenrePlaylist(nil) + a.list = widgets.NewArtistGenreList(nil) a.list.ShowAlbumCount = true a.list.ShowTrackCount = isGenresPage a.list.OnNavTo = func(id string) { @@ -103,11 +103,11 @@ func (s *savedArtistsGenresPage) Restore() Page { return NewArtistsGenresPage(s.isGenresPage, s.contr, s.sm) } -func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) []widgets.ArtistGenrePlaylistItemModel { - model := make([]widgets.ArtistGenrePlaylistItemModel, 0) +func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) []widgets.ArtistGenreListItemModel { + model := make([]widgets.ArtistGenreListItemModel, 0) for _, idx := range artists.Index { for _, artist := range idx.Artist { - model = append(model, widgets.ArtistGenrePlaylistItemModel{ + model = append(model, widgets.ArtistGenreListItemModel{ ID: artist.ID, Name: artist.Name, AlbumCount: artist.AlbumCount, @@ -118,10 +118,10 @@ func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) [ return model } -func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []widgets.ArtistGenrePlaylistItemModel { - model := make([]widgets.ArtistGenrePlaylistItemModel, 0) +func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []widgets.ArtistGenreListItemModel { + model := make([]widgets.ArtistGenreListItemModel, 0) for _, genre := range genres { - model = append(model, widgets.ArtistGenrePlaylistItemModel{ + model = append(model, widgets.ArtistGenreListItemModel{ ID: genre.Name, Name: genre.Name, AlbumCount: genre.AlbumCount, diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 6d1aa5f..f574863 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -6,6 +6,7 @@ import ( "supersonic/res" "supersonic/ui/controller" "supersonic/ui/layouts" + "supersonic/ui/util" "supersonic/ui/widgets" "time" @@ -89,7 +90,7 @@ func (a *FavoritesPage) connectGridActions() { func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) { searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer()) a.container = container.NewBorder( - container.NewHBox(widgets.NewHSpace(9), a.titleDisp, container.NewCenter(a.toggleBtns), layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), + container.NewHBox(util.NewHSpace(9), a.titleDisp, container.NewCenter(a.toggleBtns), layout.NewSpacer(), searchVbox, util.NewHSpace(15)), nil, nil, nil, initialView) } @@ -154,7 +155,7 @@ func (a *FavoritesPage) Reload() { } if a.artistListCtr != nil { // refresh favorite artists view - al := a.artistListCtr.Objects[0].(*widgets.ArtistGenrePlaylist) + al := a.artistListCtr.Objects[0].(*widgets.ArtistGenreList) al.Items = buildArtistListModel(starred.Artist) if a.toggleBtns.ActivatedButtonIndex() == 1 { // favorite artists view is visible @@ -263,7 +264,7 @@ func (a *FavoritesPage) onShowFavoriteArtists() { return } model := buildArtistListModel(s.Artist) - artistList := widgets.NewArtistGenrePlaylist(model) + artistList := widgets.NewArtistGenreList(model) artistList.ShowAlbumCount = true artistList.OnNavTo = func(artistID string) { a.contr.NavigateTo(controller.ArtistRoute(artistID)) @@ -281,10 +282,10 @@ func (a *FavoritesPage) onShowFavoriteArtists() { } } -func buildArtistListModel(artists []*subsonic.ArtistID3) []widgets.ArtistGenrePlaylistItemModel { - model := make([]widgets.ArtistGenrePlaylistItemModel, 0) +func buildArtistListModel(artists []*subsonic.ArtistID3) []widgets.ArtistGenreListItemModel { + model := make([]widgets.ArtistGenreListItemModel, 0) for _, ar := range artists { - model = append(model, widgets.ArtistGenrePlaylistItemModel{ + model = append(model, widgets.ArtistGenreListItemModel{ ID: ar.ID, Name: ar.Name, AlbumCount: ar.AlbumCount, diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index fc70b00..2056352 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -4,6 +4,7 @@ import ( "supersonic/backend" "supersonic/res" "supersonic/ui/controller" + "supersonic/ui/util" "supersonic/ui/widgets" "fyne.io/fyne/v2" @@ -70,7 +71,7 @@ func (g *GenrePage) createContainer(searchGrid bool) { } playRandomVbox := container.NewVBox(layout.NewSpacer(), g.playRandom, layout.NewSpacer()) g.container = container.NewBorder( - container.NewHBox(widgets.NewHSpace(6), g.titleDisp, playRandomVbox, layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), + container.NewHBox(util.NewHSpace(6), g.titleDisp, playRandomVbox, layout.NewSpacer(), searchVbox, util.NewHSpace(15)), nil, nil, nil, diff --git a/ui/util/util.go b/ui/util/util.go index d1db088..ced227d 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -6,6 +6,8 @@ import ( "math" "strings" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "golang.org/x/net/html" ) @@ -53,3 +55,23 @@ func RichTextSegsFromHTMLString(s string) []widget.RichTextSegment { return segs } + +type HSpace struct { + widget.BaseWidget + + Width float32 +} + +func NewHSpace(w float32) *HSpace { + h := &HSpace{Width: w} + h.ExtendBaseWidget(h) + return h +} + +func (h *HSpace) MinSize() fyne.Size { + return fyne.NewSize(h.Width, 0) +} + +func (h *HSpace) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(layout.NewSpacer()) +} diff --git a/ui/widgets/artistgenreplaylist.go b/ui/widgets/artistgenrelist.go similarity index 71% rename from ui/widgets/artistgenreplaylist.go rename to ui/widgets/artistgenrelist.go index 127c8fa..c3dbb04 100644 --- a/ui/widgets/artistgenreplaylist.go +++ b/ui/widgets/artistgenrelist.go @@ -9,7 +9,7 @@ import ( "fyne.io/fyne/v2/widget" ) -type ArtistGenrePlaylistItemModel struct { +type ArtistGenreListItemModel struct { ID string Name string AlbumCount int @@ -17,10 +17,10 @@ type ArtistGenrePlaylistItemModel struct { Favorite bool } -type ArtistGenrePlaylist struct { +type ArtistGenreList struct { widget.BaseWidget - Items []ArtistGenrePlaylistItemModel + Items []ArtistGenreListItemModel ShowAlbumCount bool ShowTrackCount bool OnNavTo func(string) @@ -31,10 +31,10 @@ type ArtistGenrePlaylist struct { container *fyne.Container } -type ArtistGenrePlaylistRow struct { +type ArtistGenreListRow struct { widget.BaseWidget - Item ArtistGenrePlaylistItemModel + Item ArtistGenreListItemModel OnTapped func() nameLabel *widget.Label @@ -44,8 +44,8 @@ type ArtistGenrePlaylistRow struct { container *fyne.Container } -func NewArtistGenrePlaylistRow(layout *layouts.ColumnsLayout) *ArtistGenrePlaylistRow { - a := &ArtistGenrePlaylistRow{ +func NewArtistGenreListRow(layout *layouts.ColumnsLayout) *ArtistGenreListRow { + a := &ArtistGenreListRow{ nameLabel: widget.NewLabel(""), albumCountLabel: widget.NewLabel(""), trackCountLabel: widget.NewLabel(""), @@ -57,8 +57,8 @@ func NewArtistGenrePlaylistRow(layout *layouts.ColumnsLayout) *ArtistGenrePlayli return a } -func NewArtistGenrePlaylist(items []ArtistGenrePlaylistItemModel) *ArtistGenrePlaylist { - a := &ArtistGenrePlaylist{ +func NewArtistGenreList(items []ArtistGenreListItemModel) *ArtistGenreList { + a := &ArtistGenreList{ Items: items, columnsLayout: layouts.NewColumnsLayout([]float32{-1, 125, 125}), } @@ -68,12 +68,12 @@ func NewArtistGenrePlaylist(items []ArtistGenrePlaylistItemModel) *ArtistGenrePl a.list = widget.NewList( func() int { return len(a.Items) }, func() fyne.CanvasObject { - r := NewArtistGenrePlaylistRow(a.columnsLayout) + r := NewArtistGenreListRow(a.columnsLayout) r.OnTapped = func() { a.onRowDoubleTapped(r.Item) } return r }, func(id widget.ListItemID, item fyne.CanvasObject) { - row := item.(*ArtistGenrePlaylistRow) + row := item.(*ArtistGenreListRow) row.Item = a.Items[id] row.albumCountLabel.Hidden = !a.ShowAlbumCount row.trackCountLabel.Hidden = !a.ShowTrackCount @@ -87,28 +87,28 @@ func NewArtistGenrePlaylist(items []ArtistGenrePlaylistItemModel) *ArtistGenrePl return a } -func (a *ArtistGenrePlaylist) Refresh() { +func (a *ArtistGenreList) Refresh() { a.hdr.SetColumnVisible(1, a.ShowAlbumCount) a.hdr.SetColumnVisible(2, a.ShowTrackCount) a.BaseWidget.Refresh() } -func (a *ArtistGenrePlaylist) onRowDoubleTapped(item ArtistGenrePlaylistItemModel) { +func (a *ArtistGenreList) onRowDoubleTapped(item ArtistGenreListItemModel) { if a.OnNavTo != nil { a.OnNavTo(item.ID) } } -func (a *ArtistGenrePlaylistRow) Tapped(*fyne.PointEvent) { +func (a *ArtistGenreListRow) Tapped(*fyne.PointEvent) { if a.OnTapped != nil { a.OnTapped() } } -func (a *ArtistGenrePlaylist) CreateRenderer() fyne.WidgetRenderer { +func (a *ArtistGenreList) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(a.container) } -func (a *ArtistGenrePlaylistRow) CreateRenderer() fyne.WidgetRenderer { +func (a *ArtistGenreListRow) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(a.container) } diff --git a/ui/widgets/hspace.go b/ui/widgets/hspace.go deleted file mode 100644 index 8ea39c3..0000000 --- a/ui/widgets/hspace.go +++ /dev/null @@ -1,27 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/widget" -) - -type HSpace struct { - widget.BaseWidget - - Width float32 -} - -func NewHSpace(w float32) *HSpace { - h := &HSpace{Width: w} - h.ExtendBaseWidget(h) - return h -} - -func (h *HSpace) MinSize() fyne.Size { - return fyne.NewSize(h.Width, 0) -} - -func (h *HSpace) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(layout.NewSpacer()) -} diff --git a/ui/widgets/searchentry.go b/ui/widgets/searchentry.go deleted file mode 100644 index 81ac28b..0000000 --- a/ui/widgets/searchentry.go +++ /dev/null @@ -1,60 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" -) - -type SearchEntry struct { - widget.Entry - height float32 -} - -func NewSearchEntry() *SearchEntry { - sf := &SearchEntry{} - sf.ExtendBaseWidget(sf) - // this is a bit hacky - sf.height = widget.NewEntry().MinSize().Height - sf.PlaceHolder = "Search" - c := NewClearTextButton() - c.OnTapped = func() { - sf.SetText("") - } - sf.ActionItem = c - return sf -} - -func (s *SearchEntry) Refresh() { - if s.Text == "" { - s.ActionItem.(*clearTextButton).Resource = theme.SearchIcon() - } else { - s.ActionItem.(*clearTextButton).Resource = theme.ContentClearIcon() - } - s.Entry.Refresh() -} - -func (s *SearchEntry) MinSize() fyne.Size { - return fyne.NewSize(200, s.height) -} - -var _ fyne.Tappable = (*clearTextButton)(nil) - -type clearTextButton struct { - widget.Icon - - OnTapped func() -} - -func NewClearTextButton() *clearTextButton { - c := &clearTextButton{} - c.ExtendBaseWidget(c) - c.Resource = theme.SearchIcon() - return c -} - -func (c *clearTextButton) Tapped(*fyne.PointEvent) { - if c.OnTapped != nil { - c.OnTapped() - } -} diff --git a/ui/widgets/searcher.go b/ui/widgets/searcher.go index 49aa1b0..0d828a4 100644 --- a/ui/widgets/searcher.go +++ b/ui/widgets/searcher.go @@ -3,8 +3,15 @@ package widgets import ( "sync" "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" ) +// Searcher is a search entry widget that will issue a search command +// (aka call OnSearched) when a short span of time has elapsed since +// the user typed into the widget. type Searcher struct { Entry *SearchEntry OnSearched func(string) @@ -61,3 +68,56 @@ func (s *Searcher) sendSearch(text string) { s.OnSearched(text) } } + +type SearchEntry struct { + widget.Entry + height float32 +} + +func NewSearchEntry() *SearchEntry { + sf := &SearchEntry{} + sf.ExtendBaseWidget(sf) + // this is a bit hacky + sf.height = widget.NewEntry().MinSize().Height + sf.PlaceHolder = "Search" + c := NewClearTextButton() + c.OnTapped = func() { + sf.SetText("") + } + sf.ActionItem = c + return sf +} + +func (s *SearchEntry) Refresh() { + if s.Text == "" { + s.ActionItem.(*clearTextButton).Resource = theme.SearchIcon() + } else { + s.ActionItem.(*clearTextButton).Resource = theme.ContentClearIcon() + } + s.Entry.Refresh() +} + +func (s *SearchEntry) MinSize() fyne.Size { + return fyne.NewSize(200, s.height) +} + +var _ fyne.Tappable = (*clearTextButton)(nil) + +type clearTextButton struct { + widget.Icon + + OnTapped func() +} + +func NewClearTextButton() *clearTextButton { + c := &clearTextButton{} + c.ExtendBaseWidget(c) + c.Resource = theme.SearchIcon() + return c +} + +func (c *clearTextButton) Tapped(*fyne.PointEvent) { + if c.OnTapped != nil { + c.OnTapped() + } +} diff --git a/ui/widgets/tappableicon.go b/ui/widgets/tappableicon.go deleted file mode 100644 index 4ca70f3..0000000 --- a/ui/widgets/tappableicon.go +++ /dev/null @@ -1,34 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/widget" -) - -type TappableIcon struct { - widget.Icon - - OnTapped func() -} - -func NewTappbaleIcon(res fyne.Resource) *TappableIcon { - icon := &TappableIcon{} - icon.ExtendBaseWidget(icon) - icon.SetResource(res) - - return icon -} - -func (t *TappableIcon) Tapped(_ *fyne.PointEvent) { - if t.OnTapped != nil { - t.OnTapped() - } -} - -func (t *TappableIcon) TappedSecondary(_ *fyne.PointEvent) { -} - -func (t *TappableIcon) Cursor() desktop.Cursor { - return desktop.PointerCursor -} diff --git a/ui/widgets/tappableimage.go b/ui/widgets/tappablewrappers.go similarity index 59% rename from ui/widgets/tappableimage.go rename to ui/widgets/tappablewrappers.go index d17d173..218449a 100644 --- a/ui/widgets/tappableimage.go +++ b/ui/widgets/tappablewrappers.go @@ -7,6 +7,35 @@ import ( "fyne.io/fyne/v2/widget" ) +// TappableIcon is a tappable wrapper of widget.Icon +type TappableIcon struct { + widget.Icon + + OnTapped func() +} + +func NewTappbaleIcon(res fyne.Resource) *TappableIcon { + icon := &TappableIcon{} + icon.ExtendBaseWidget(icon) + icon.SetResource(res) + + return icon +} + +func (t *TappableIcon) Tapped(_ *fyne.PointEvent) { + if t.OnTapped != nil { + t.OnTapped() + } +} + +func (t *TappableIcon) TappedSecondary(_ *fyne.PointEvent) { +} + +func (t *TappableIcon) Cursor() desktop.Cursor { + return desktop.PointerCursor +} + +// TappableImage is a tappable wrapper of canvas.Image type TappableImage struct { widget.BaseWidget canvas.Image diff --git a/ui/widgets/thickseparator.go b/ui/widgets/thickseparator.go deleted file mode 100644 index 95a1fcb..0000000 --- a/ui/widgets/thickseparator.go +++ /dev/null @@ -1,28 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" -) - -type ThickSeparator struct { - widget.BaseWidget - line canvas.Line -} - -func NewThickSeparator() *ThickSeparator { - t := &ThickSeparator{ - line: canvas.Line{ - StrokeWidth: 3, - StrokeColor: theme.DisabledColor(), - }, - } - t.ExtendBaseWidget(t) - return t -} - -func (t *ThickSeparator) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(&t.line) -} diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 23fd1ed..16096e9 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -32,10 +32,19 @@ const ( type Tracklist struct { widget.BaseWidget - Tracks []*subsonic.Child + // Tracks is the set of tracks displayed by the widget. + Tracks []*subsonic.Child + + // AutoNumber sets whether to auto-number the tracks 1..N in display order, + // or to use the number from the track's metadata AutoNumber bool + + // AuxiliaryMenuItems sets additional menu items appended to the context menu // must be set before the context menu is shown for the first time - AuxiliaryMenuItems []*fyne.MenuItem + AuxiliaryMenuItems []*fyne.MenuItem + + // DisablePlaybackMenu sets whether to disable playback options in + // the tracklist context menu. DisablePlaybackMenu bool // user action callbacks @@ -75,7 +84,7 @@ func NewTracklist(tracks []*subsonic.Child) *Tracklist { t.OnColumnVisibilityMenuShown(pop) } } - playingIcon := container.NewCenter(container.NewHBox(NewHSpace(2), widget.NewIcon(theme.MediaPlayIcon()))) + playingIcon := container.NewCenter(container.NewHBox(util.NewHSpace(2), widget.NewIcon(theme.MediaPlayIcon()))) t.list = widget.NewList( func() int { return len(t.Tracks) }, func() fyne.CanvasObject {