Merge branch 'dweymouth:main' into main

This commit is contained in:
Benoît Smith
2024-08-06 22:54:39 +02:00
committed by GitHub
26 changed files with 805 additions and 137 deletions
+1
View File
@@ -31,6 +31,7 @@ Supersonic supports any music server with a Subsonic (or OpenSubsonic) API, or J
* [Jellyfin](https://jellyfin.org) * [Jellyfin](https://jellyfin.org)
* [Gonic](https://github.com/sentriz/gonic) * [Gonic](https://github.com/sentriz/gonic)
* [LMS](https://github.com/epoupon/lms) * [LMS](https://github.com/epoupon/lms)
* [Nextcloud Music](https://apps.nextcloud.com/apps/music)
* [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced) * [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
* [Ampache](https://ampache.org) * [Ampache](https://ampache.org)
* [Funkwhale](https://www.funkwhale.audio/) * [Funkwhale](https://www.funkwhale.audio/)
@@ -12,7 +12,7 @@ func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider.
return &allTracksIterator{ return &allTracksIterator{
s: s, s: s,
albumIter: s.IterateAlbums( albumIter: s.IterateAlbums(
mediaprovider.AlbumSortArtistAZ, mediaprovider.AlbumSortRecentlyAdded,
mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{}), mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{}),
), ),
} }
+3 -2
View File
@@ -8,8 +8,9 @@ require (
github.com/Microsoft/go-winio v0.6.2 github.com/Microsoft/go-winio v0.6.2
github.com/cenkalti/dominantcolor v1.0.3 github.com/cenkalti/dominantcolor v1.0.3
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
github.com/dweymouth/fyne-tooltip v0.2.0
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf
github.com/dweymouth/go-subsonic v0.0.0-20240726004217-2e8e348ad417 github.com/dweymouth/go-subsonic v0.0.0-20240726004217-2e8e348ad417
@@ -53,4 +54,4 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139 replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e
+6 -4
View File
@@ -75,12 +75,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI=
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E=
github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe h1:owGwqph+Y+PqjDiWjZjOFOhlo8QsLs+LHrHojaaBo34= github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1 h1:NTiWy47NaM2WoJ4Rjlx/4QmtHpNLl6ujptmIirDJ9vk=
github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o= github.com/dweymouth/fyne-advanced-list v0.0.0-20240806013530-392de9d6a2a1/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o=
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg=
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139 h1:JNOd+yJlurYrdAuOiibR6/EYl+/+idFkk+wwyXiNwIQ= github.com/dweymouth/fyne-tooltip v0.2.0 h1:6Zy3gryctuPoQfYf8Xp3tjenioebMt11NBGW/QXIvxE=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240728183020-7abcad1f4139/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc= github.com/dweymouth/fyne-tooltip v0.2.0/go.mod h1:zEgy7p9tSVIuy2GufFbOCoK3Q04zhyDPOotlU4G3Ma4=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e h1:FSTLNY9xV0+4/x9jKPXqUpwPZfhkAMz5ZnzLBkRirMw=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240805144743-24df77c3cc7e/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc=
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs=
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf h1:QVjXWx7XGkSPOCEu5EL9QVY3fkdDSnG5KEkok5o1svM= github.com/dweymouth/go-mpv v0.0.0-20240724002347-c5e5b36f1bbf h1:QVjXWx7XGkSPOCEu5EL9QVY3fkdDSnG5KEkok5o1svM=
+11 -1
View File
@@ -28,6 +28,7 @@
"Authentication failed": "Authentication failed", "Authentication failed": "Authentication failed",
"Auto": "Auto", "Auto": "Auto",
"Autoselect device": "Autoselect device", "Autoselect device": "Autoselect device",
"Back": "Back",
"Bit rate": "Bit rate", "Bit rate": "Bit rate",
"BPM": "BPM", "BPM": "BPM",
"Broadcast": "Broadcast", "Broadcast": "Broadcast",
@@ -73,7 +74,9 @@
"Field Recording": "Field Recording", "Field Recording": "Field Recording",
"File path": "File path", "File path": "File path",
"File size": "File size", "File size": "File size",
"Filter albums": "Filter albums",
"Filter genres": "Filter genres", "Filter genres": "Filter genres",
"Forward": "Forward",
"Frequently Played": "Frequently Played", "Frequently Played": "Frequently Played",
"General": "General", "General": "General",
"Genre": "Genre", "Genre": "Genre",
@@ -81,6 +84,7 @@
"Github page": "Github page", "Github page": "Github page",
"Go to release page": "Go to release page", "Go to release page": "Go to release page",
"Hide": "Hide", "Hide": "Hide",
"Home": "Home",
"Home Page": "Home Page", "Home Page": "Home Page",
"hr": "hr", "hr": "hr",
"hrs": "hrs", "hrs": "hrs",
@@ -95,9 +99,11 @@
"Login to Server": "Login to Server", "Login to Server": "Login to Server",
"Lyrics": "Lyrics", "Lyrics": "Lyrics",
"Lyrics not available": "Lyrics not available", "Lyrics not available": "Lyrics not available",
"Menu": "Menu",
"min": "min", "min": "min",
"minutes of track have been played": "minutes of track have been played", "minutes of track have been played": "minutes of track have been played",
"Mixtape": "Mixtape", "Mixtape": "Mixtape",
"Mute": "Mute",
"My Server": "My Server", "My Server": "My Server",
"Name": "Name", "Name": "Name",
"Name (A-Z)": "Name (A-Z)", "Name (A-Z)": "Name (A-Z)",
@@ -139,6 +145,7 @@
"Recently Added": "Recently Added", "Recently Added": "Recently Added",
"Recently Played": "Recently Played", "Recently Played": "Recently Played",
"Related": "Related", "Related": "Related",
"Reload": "Reload",
"Remix": "Remix", "Remix": "Remix",
"Remove from playlist": "Remove from playlist", "Remove from playlist": "Remove from playlist",
"ReplayGain mode": "ReplayGain mode", "ReplayGain mode": "ReplayGain mode",
@@ -163,6 +170,7 @@
"Show": "Show", "Show": "Show",
"Show info": "Show info", "Show info": "Show info",
"Show notification on track change": "Show notification on track change", "Show notification on track change": "Show notification on track change",
"Show play queue": "Show play queue",
"Show year in album grid cards": "Show year in album grid cards", "Show year in album grid cards": "Show year in album grid cards",
"Shuffle": "Shuffle", "Shuffle": "Shuffle",
"Shuffle albums": "Shuffle albums", "Shuffle albums": "Shuffle albums",
@@ -216,5 +224,7 @@
"Set favorite": "Set favorite", "Set favorite": "Set favorite",
"Unset favorite": "Unset favorite", "Unset favorite": "Unset favorite",
"Remove from queue": "Remove from queue", "Remove from queue": "Remove from queue",
"Play song radio": "Play song radio" "Play song radio": "Play song radio",
"to":"to",
"by":"by"
} }
+222
View File
@@ -0,0 +1,222 @@
{
"A new version is available": "Nowa wersja jest dostępna",
"About": "O aplikacji",
"Add Server": "Dodaj serwer",
"Add to playlist": "Dodaj do playlisty",
"Add to queue": "Dodaj do odtwarzania",
"Album": "Album",
"album": "album",
"Album count": "Ilość albumów",
"Album Count": "Ilość Albumów",
"Album filters": "Filtry albumu",
"Album gain": "Album gain",
"Album info not available": "Informacje ablumu niedostępne",
"Album peak": "Album peak",
"Albums": "Albumy",
"albums": "albumy",
"All": "Wszystko",
"All Tracks": "Wszystkie ",
"Alt. URL": "Alt. URL",
"Are you sure you want to delete the server": "Czy jesteś pewny, że chcesz usunąć serwer",
"Artist": "Artysta",
"Artist (A-Z)": "Artyści (A-Z)",
"Artists": "Artyści",
"Artist biography not available.": "Biografia artysty niedostępna.",
"Audio device": "Urządzenie audio",
"Audio Drama": "Audio Drama",
"Audiobook": "Audiobook",
"Authentication failed": "Błąd autoryzacji",
"Auto": "Auto",
"Autoselect device": "Automatyczny wybór",
"Bit rate": "Bit rate",
"BPM": "BPM",
"Broadcast": "Broadcast",
"Cancel": "Anuluj",
"Check for Updates": "Sprawdź aktualizacje",
"Close": "Zamknij",
"Close to system tray": "Zamknij do zasobnika systemowego",
"Comment": "Komentarz",
"Compilation": "Compilation",
"Composer": "Composer",
"Configure your music server to add radio stations": "Skonfiguruj swój serwer muzyki aby dodać stacje radiowe",
"Confirm Delete Playlist": "Potwierdź usunięcie playlisty",
"Confirm Delete Server": "Potwierdź usunięcie serwera",
"Connect to Server": "Połącz z Serwerem",
"Connecting": "Łączenie",
"Connecting to": "Łączenie z",
"Content type": "Typ pliku",
"Could not reach server": "Nie można dosięgnąć serwera",
"Create new playlist": "Utwórz nową playlistę",
"day": "dzień",
"days": "dni",
"Delete Playlist": "Usuń playlistę",
"Demo": "Demo",
"Description": "Opis",
"Disable server transcoding": "Wyłącz transkodowanie serwera",
"Disc number": "Disc number",
"Discography": "Dyskografia",
"discs": "discs",
"DJ-Mix": "DJ-Mix",
"Download": "Pobierz",
"Download completed": "Pobieranie zakończone",
"Duration": "Czas trwania",
"Edit": "Edytuj",
"Edit Playlist": "Edytuj playlistę",
"Edit server": "Edytuj serwer",
"Enable system tray": "Włącz chowanie do zasobnika",
"Enabled": "Włączony",
"Enter": "Enter",
"EP": "EP",
"Equalizer": "Equalizer",
"Exclusive mode": "Tryb exclusive",
"Favorites": "Ulubione",
"Field Recording": "Field Recording",
"File path": "Ścieżka pliku",
"File size": "Wielkość pliku",
"Filter genres": "Filtruj gatunki",
"Frequently Played": "Często odtwarzane",
"General": "Ogólny",
"Genre": "Gatunek",
"Genres": "Gatunki",
"Github page": "Strona githuba",
"Go to release page": "Otwórz stronę z wersjami",
"Hide": "Ukryj",
"Home Page": "Strona domowa",
"hr": "hr",
"hrs": "hrs",
"Internet Radio Stations": "Internetowe Stacje Radiowe",
"Interview": "Wywiad",
"Is favorite": "Jest ulubione",
"Is not favorite": "Nie jest ulubione",
"Last played": "Ostatnio odtwarzane",
"Live": "Live",
"Locally": "Lokalnie",
"Log Out": "Wyloguj",
"Login to Server": "Login do Serwera",
"Lyrics": "Tekst piosenki",
"Lyrics not available": "Tekst piosenki nie jest dostępny",
"min": "min",
"minutes of track have been played": "minuty utworu odtworzono",
"Mixtape": "Mixtape",
"My Server": "Mój serwer",
"Name": "Nazwa",
"Name (A-Z)": "Nazwa (A-Z)",
"Next": "Następny",
"Nickname": "Nazwa serwera",
"No new version found": "Nie znaleziono nowych wersji",
"No radio stations available": "Brak dostępnych radio stacji",
"None": "Nic",
"none": "nic",
"none selected": "wybrano nic",
"OK": "OK",
"optional": "opcjonalne",
"or when": "lub kiedy",
"Password": "Hasło",
"Pause": "Pauza",
"Paused": "Wstrzymane",
"Peak Meter": "Licznik szczytowy",
"percent of track is played": "procent odtworzonej piosenki",
"Play": "Odtwórz",
"Play Artist Radio": "Odtwórz radio artysty",
"Play count": "Ilość odtworzeń",
"Play Discography": "Odtwórz Dyskografię",
"Play next": "Odtwórz następne",
"Play Queue": "Lista odtwarzania",
"Play random": "Odtwórz losowo",
"Playback": "Playback",
"Playing": "Odtwarza",
"Playlist": "Playlista",
"Playlists": "Playlisty",
"playlist by": "playlista przez",
"Plays": "Ilość odtworzeń",
"Prevent clipping": "Zapobiegnij przycicnaniu",
"Previous": "Poprzednie",
"Private": "Prywatna",
"Public": "Publiczne",
"Random": "Losowe",
"Rating": "Ocena",
"reissued": "odnowiony",
"Recently Added": "Ostatnio dodane",
"Recently Played": "Ostatnio odtwarzane",
"Related": "Powiązane",
"Remix": "Remix",
"Remove from playlist": "Usuń z playlisty",
"ReplayGain mode": "ReplayGain mode",
"ReplayGain preamp": "ReplayGain preamp",
"Restart required": "Wymagany restart",
"Save play queue on exit": "Zapisz kolejkę odtwarzania po zamknięciu",
"Saved at": "Zapisano w",
"Scrobble when": "Wyślij gdy",
"Search Everywhere": "Szukaj wszędzie",
"Search page": "Przeszukaj stronę",
"Search playlists or new playlist name": "Szukaj playlisty lub",
"sec": "sec",
"selected": "wybrano",
"Send playback statistics to server": "Wyślij statystyki playbacku do serwera",
"Server": "Serwer",
"Server Type": "Typ serwera",
"Server unreachable": "Server nieosiągalny",
"Set rating": "Ustaw ocenę",
"Settings": "Ustawienia",
"Share": "Udostępnij",
"Share content": "Share content",
"Show": "Pokaż",
"Show info": "Pokaż informacje",
"Show notification on track change": "Pokaż powiadomienie o zmianie ścieżki",
"Show year in album grid cards": "Pokaż rok w zakładce albumu",
"Shuffle": "Wymieszaj",
"Shuffle albums": "Wymieszaj album",
"Shuffle tracks": "Wymieszaj ścieżki",
"Similar artists": "Podobni artyści",
"Single": "Single",
"Size": "Wielkość",
"Skip duplicate tracks": "Pomiń zduplikowane ścieżki",
"Skip this version": "Pomiń tą wersję",
"Soundtrack": "Soundtrack",
"Spoken Word": "Spoken Word",
"Startup page": "Strona startowa",
"Stopped": "Wstrzymane",
"Support the project": "Wesprzyj projekt",
"Switch Servers": "Zmień serwer",
"Testing connection": "Testowanie połączenia",
"Theme": "Motyw",
"Time": "Czas",
"Title": "Tytuł",
"Title (A-Z)": "Tytuł (A-Z)",
"Top Tracks": "Top utworów",
"Total time": "Pełny czas",
"Track": "Utwór",
"track": "utwór",
"Track count": "Ilość utworów",
"Track gain": "Track gain",
"Track Info": "Informacje utworu",
"Track number": "Numer utworu",
"Track peak": "Track peak",
"tracks": "utwory",
"UI Scaling": "Skalowanie UI",
"URL": "URL",
"Use legacy authentication": "Użyj starsze uwierzytelnianie",
"Username": "Login",
"version": "wersja",
"Visualizations": "Wizualizacja",
"Volume": "Głośność",
"wrong URL": "Nieprawidłowy URL",
"wrong username/password": "Nieprawidłowy login/hasło",
"Year": "Rok",
"Year (ascending)": "Rok (rosnąco)",
"Year (descending)": "Rok (malejąco)",
"Year from": "Z roku",
"You are running the latest version of": "Korzystasz z najnowszej wersji",
"Quit": "Wyjdź",
"Rescan Library": "Przeszukaj ponownie bibliotekę",
"Search": "Szukaj",
"Owner": "Właściciel",
"To server": "Do serwera",
"Fav.": "Ulub.",
"Set favorite": "Ustaw ulubione",
"Unset favorite": "Usuń ulubione",
"Remove from queue": "Usuń z kolejki odtwarzania",
"Play song radio": "Odtwórz mix utworów",
"to":"do",
"by":"przez"
}
+222
View File
@@ -0,0 +1,222 @@
{
"A new version is available": "O nouă versiune este disponibilă",
"About": "Despre",
"Add Server": "Adaugă server",
"Add to playlist": "Adaugă în playlist",
"Add to queue": "Adaugă în lista de redare",
"Album": "Album",
"album": "album",
"Album count": "Număr album",
"Album Count": "Număr album",
"Album filters": "Filtre album",
"Album gain": "Nivel volum album",
"Album info not available": "Informații despre album indisponibile",
"Album peak": "Vârf album",
"Albums": "Albume",
"albums": "albume",
"All": "Toate",
"All Tracks": "Toate melodiile",
"Alt. URL": "URL alt.",
"Are you sure you want to delete the server": "Ești sigur că vrei să ștergi serverul",
"Artist": "Artist",
"Artist (A-Z)": "Artist (A-Z)",
"Artists": "Artiști",
"Artist biography not available.": "Biografia artistului indisponibilă.",
"Audio device": "Dispozitiv audio",
"Audio Drama": "Dramă audio",
"Audiobook": "Audiobook",
"Authentication failed": "Autentificare eșuată",
"Auto": "Auto",
"Autoselect device": "Selectează automat dispozitiv",
"Bit rate": "Bit rate",
"BPM": "BPM",
"Broadcast": "Transmisiune",
"Cancel": "Anulează",
"Check for Updates": "Verifică actualizări",
"Close": "Închide",
"Close to system tray": "Închide în bara de sistem",
"Comment": "Comentariu",
"Compilation": "Compilație",
"Composer": "Compozitor",
"Configure your music server to add radio stations": "Configurează-ți serverul de muzică pentru a adăuga stații radio",
"Confirm Delete Playlist": "Confirmare ștergere playlist",
"Confirm Delete Server": "Confirmare ștergere server",
"Connect to Server": "Conectare la server",
"Connecting": "Conectare",
"Connecting to": "Conectare la",
"Content type": "Tip conținut",
"Could not reach server": "Serverul nu a putut fi contactat",
"Create new playlist": "Creează playlist nou",
"day": "zi",
"days": "zile",
"Delete Playlist": "Șterge playlist",
"Demo": "Demo",
"Description": "Descriere",
"Disable server transcoding": "Dezactivează transcodare pe server",
"Disc number": "Număr disc",
"Discography": "Discografie",
"discs": "discuri",
"DJ-Mix": "DJ-Mix",
"Download": "Descarcă",
"Download completed": "Descărcare completă",
"Duration": "Durată",
"Edit": "Editare",
"Edit Playlist": "Editează playlist",
"Edit server": "Editează server",
"Enable system tray": "Activare bara de sistem",
"Enabled": "Activat",
"Enter": "Enter",
"EP": "EP",
"Equalizer": "Egalizator",
"Exclusive mode": "Mod exclusiv",
"Favorites": "Favorite",
"Field Recording": "Înregistrare teren",
"File path": "Locație fișier",
"File size": "Mărime fișier",
"Filter genres": "Filtrează genuri",
"Frequently Played": "Redate frecvent",
"General": "General",
"Genre": "Gen",
"Genres": "Genuri",
"Github page": "Pagina Github",
"Go to release page": "Mergi la pagina de release-uri",
"Hide": "Ascunde",
"Home Page": "Pagina principală",
"hr": "oră",
"hrs": "ore",
"Internet Radio Stations": "Stații radio pe internet",
"Interview": "Interviu",
"Is favorite": "Este favorit",
"Is not favorite": "Nu este favorit",
"Last played": "Ultimul redat",
"Live": "Live",
"Locally": "Local",
"Log Out": "Deconectare",
"Login to Server": "Conectare la server",
"Lyrics": "Versuri",
"Lyrics not available": "Versuri indisponibile",
"min": "min",
"minutes of track have been played": "minute din melodie au fost redate",
"Mixtape": "Mixtape",
"My Server": "Serverul meu",
"Name": "Nume",
"Name (A-Z)": "Nume (A-Z)",
"Next": "Următorul",
"Nickname": "Supranume",
"No new version found": "Nicio versiune nouă",
"No radio stations available": "Nicio stație radio disponibilă",
"None": "Nimic",
"none": "nimic",
"none selected": "nimic selectat",
"OK": "OK",
"optional": "opțional",
"or when": "sau când",
"Password": "Parolă",
"Pause": "Pauză",
"Paused": "Oprit",
"Peak Meter": "Indicator de vârf",
"percent of track is played": "la sută din melodie a fost redat",
"Play": "Redare",
"Play Artist Radio": "Redă radio artist",
"Play count": "Număr redări",
"Play Discography": "Redă discografia",
"Play next": "Redă următorul",
"Play Queue": "Redă lista de redare",
"Play random": "Redă aleatoriu",
"Playback": "Playback",
"Playing": "Redare",
"Playlist": "Playlist",
"Playlists": "Playlist-uri",
"playlist by": "playlist de",
"Plays": "Redări",
"Prevent clipping": "Previne distorsiunea",
"Previous": "Anterior",
"Private": "Privat",
"Public": "Public",
"Random": "Aleatoriu",
"Rating": "Rating",
"reissued": "reeditat",
"Recently Added": "Adăugat recent",
"Recently Played": "Redat recent",
"Related": "Asociat",
"Remix": "Remix",
"Remove from playlist": "Șterge din playlist",
"ReplayGain mode": "Mod ReplayGain",
"ReplayGain preamp": "Preamplificare ReplayGain",
"Restart required": "Repornire necesară",
"Save play queue on exit": "Salvează listă de redare la ieșire",
"Saved at": "Salvat la",
"Scrobble when": "Scrobblare când",
"Search Everywhere": "Caută peste tot",
"Search page": "Pagină căutare",
"Search playlists or new playlist name": "Caută playlisturi sau nume de playlist nou",
"sec": "sec",
"selected": "selectat",
"Send playback statistics to server": "Trimite statistici de redare la server",
"Server": "Server",
"Server Type": "Tip server",
"Server unreachable": "Server indisponibil",
"Set rating": "Setează rating",
"Settings": "Setări",
"Share": "Partajează",
"Share content": "Partajează conținut",
"Show": "Arată",
"Show info": "Arată informații",
"Show notification on track change": "Arată notificare la schimbarea melodiei",
"Show year in album grid cards": "Arată anul în gridul de albume",
"Shuffle": "Amestecare",
"Shuffle albums": "Amestecă albume",
"Shuffle tracks": "Amestecă melodii",
"Similar artists": "Similar artists",
"Single": "Single",
"Size": "Mărime",
"Skip duplicate tracks": "Sari peste melodii duplicate",
"Skip this version": "Sari peste această versiune",
"Soundtrack": "Soundtrack",
"Spoken Word": "Cuvânt vorbit",
"Startup page": "Pagina de start",
"Stopped": "Oprit",
"Support the project": "Sprijină proiectul",
"Switch Servers": "Schimbă server-uri",
"Testing connection": "Testare conexiune",
"Theme": "Temă",
"Time": "Timp",
"Title": "Titlu",
"Title (A-Z)": "Titlu (A-Z)",
"Top Tracks": "Top melodii",
"Total time": "Timp total",
"Track": "Melodie",
"track": "melodie",
"Track count": "Număr melodii",
"Track gain": "Nivel volum melodie",
"Track Info": "Informații melodie",
"Track number": "Număr melodie",
"Track peak": "Vârf melodie",
"tracks": "melodii",
"UI Scaling": "Scalare interfață",
"URL": "URL",
"Use legacy authentication": "Folosește autentificarea de bază",
"Username": "Nume utilizator",
"version": "versiune",
"Visualizations": "Vizualizări",
"Volume": "Volum",
"wrong URL": "URL greșit",
"wrong username/password": "nume utilizator sau parolă greșite",
"Year": "An",
"Year (ascending)": "An (ascendent)",
"Year (descending)": "An (Descendent)",
"Year from": "An de la",
"You are running the latest version of": "Folosești ultima versiune de",
"Quit": "Ieșire",
"Rescan Library": "Rescanează librăria",
"Search": "Caută",
"Owner": "Deținător",
"To server": "Către server",
"Fav.": "Fav.",
"Set favorite": "Setează favorit",
"Unset favorite": "Șterge favorit",
"Remove from queue": "Șterge din lista de redare",
"Play song radio": "Redă melodie radio",
"to": "la",
"by": "de"
}
+1 -1
View File
@@ -213,7 +213,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
} }
a.releaseTypeLabel = widget.NewRichText( a.releaseTypeLabel = widget.NewRichText(
&widget.TextSegment{Text: lang.L("Album"), Style: util.BoldRichTextStyle}, &widget.TextSegment{Text: lang.L("Album"), Style: util.BoldRichTextStyle},
&widget.TextSegment{Text: " by", Style: widget.RichTextStyle{Inline: true}}, &widget.TextSegment{Text: " " + lang.L("by"), Style: widget.RichTextStyle{Inline: true}},
) )
a.artistLabel = widgets.NewMultiHyperlink() a.artistLabel = widgets.NewMultiHyperlink()
a.artistLabel.OnTapped = func(id string) { a.artistLabel.OnTapped = func(id string) {
+25 -15
View File
@@ -3,6 +3,7 @@ package browsing
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -11,6 +12,8 @@ import (
"github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
) )
type Page interface { type Page interface {
@@ -60,14 +63,14 @@ type BrowsingPane struct {
curPage Page curPage Page
home *widget.Button home *ttwidget.Button
forward *widget.Button forward *ttwidget.Button
back *widget.Button back *ttwidget.Button
reload *widget.Button reload *ttwidget.Button
history []SavedPage history []SavedPage
historyIdx int historyIdx int
settingsBtn *widget.Button settingsBtn *ttwidget.Button
settingsMenu *fyne.Menu settingsMenu *fyne.Menu
navBtnsContainer *fyne.Container navBtnsContainer *fyne.Container
pageContainer *fyne.Container pageContainer *fyne.Container
@@ -78,22 +81,28 @@ type BrowsingPane struct {
func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome func()) *BrowsingPane { func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome func()) *BrowsingPane {
b := &BrowsingPane{app: app} b := &BrowsingPane{app: app}
b.ExtendBaseWidget(b) b.ExtendBaseWidget(b)
b.home = widget.NewButtonWithIcon("", theme.HomeIcon(), onGoHome) b.home = ttwidget.NewButtonWithIcon("", theme.HomeIcon(), onGoHome)
b.back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack) b.home.SetToolTip(lang.L("Home"))
b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward) b.back = ttwidget.NewButtonWithIcon("", theme.NavigateBackIcon(), b.GoBack)
b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload) b.back.SetToolTip(lang.L("Back"))
b.forward = ttwidget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
b.forward.SetToolTip(lang.L("Forward"))
b.reload = ttwidget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload)
b.reload.SetToolTip(lang.L("Reload"))
b.app.PlaybackManager.OnSongChange(b.onSongChange) b.app.PlaybackManager.OnSongChange(b.onSongChange)
b.app.PlaybackManager.OnPlayTimeUpdate(b.onPlayTimeUpdate) b.app.PlaybackManager.OnPlayTimeUpdate(b.onPlayTimeUpdate)
b.app.PlaybackManager.OnQueueChange(b.onQueueChange) b.app.PlaybackManager.OnQueueChange(b.onQueueChange)
bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground) bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground)
b.pageContainer = container.NewStack(bkgrnd, layout.NewSpacer()) b.pageContainer = container.NewStack(bkgrnd, layout.NewSpacer())
b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() { b.settingsBtn = ttwidget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
p := widget.NewPopUpMenu(b.settingsMenu, p := widget.NewPopUpMenu(b.settingsMenu,
fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn)) fyne.CurrentApp().Driver().CanvasForObject(b.settingsBtn))
p.ShowAtPosition(fyne.NewPos(b.Size().Width-p.MinSize().Width+4, p.ShowAtPosition(fyne.NewPos(b.Size().Width-p.MinSize().Width+4,
b.navBtnsContainer.MinSize().Height+theme.Padding())) b.navBtnsContainer.MinSize().Height+theme.Padding()))
}) })
quickSearchBtn := widget.NewButtonWithIcon("", theme.SearchIcon(), contr.ShowQuickSearch) b.settingsBtn.SetToolTip(lang.L("Menu"))
quickSearchBtn := ttwidget.NewButtonWithIcon("", theme.SearchIcon(), contr.ShowQuickSearch)
quickSearchBtn.SetToolTip(lang.L("Search Everywhere"))
b.settingsMenu = fyne.NewMenu("") b.settingsMenu = fyne.NewMenu("")
b.navBtnsContainer = container.NewHBox() b.navBtnsContainer = container.NewHBox()
b.navBtnsPageMap = map[controller.PageName]fyne.Resource{} b.navBtnsPageMap = map[controller.PageName]fyne.Resource{}
@@ -146,10 +155,11 @@ func (b *BrowsingPane) AddSettingsMenuSeparator() {
fyne.NewMenuItemSeparator()) fyne.NewMenuItemSeparator())
} }
func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *widget.Button { func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *ttwidget.Button {
// make a copy of the icon, because it can change the color // make a copy of the icon, because it can change the color
browsingPaneIcon := theme.NewThemedResource(icon) browsingPaneIcon := theme.NewThemedResource(icon)
btn := widget.NewButtonWithIcon("", browsingPaneIcon, action) btn := ttwidget.NewButtonWithIcon("", browsingPaneIcon, action)
btn.SetToolTip(lang.L(pageName.String()))
b.navBtnsContainer.Add(btn) b.navBtnsContainer.Add(btn)
b.navBtnsPageMap[pageName] = browsingPaneIcon b.navBtnsPageMap[pageName] = browsingPaneIcon
return btn return btn
@@ -157,13 +167,13 @@ func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controll
func (b *BrowsingPane) DisableNavigationButtons() { func (b *BrowsingPane) DisableNavigationButtons() {
for _, obj := range b.navBtnsContainer.Objects { for _, obj := range b.navBtnsContainer.Objects {
obj.(*widget.Button).Disable() obj.(fyne.Disableable).Disable()
} }
} }
func (b *BrowsingPane) EnableNavigationButtons() { func (b *BrowsingPane) EnableNavigationButtons() {
for _, obj := range b.navBtnsContainer.Objects { for _, obj := range b.navBtnsContainer.Objects {
obj.(*widget.Button).Enable() obj.(fyne.Disableable).Enable()
} }
} }
+42 -28
View File
@@ -14,6 +14,7 @@ import (
"sync" "sync"
"time" "time"
fynetooltip "github.com/dweymouth/fyne-tooltip"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/backend/player" "github.com/dweymouth/supersonic/backend/player"
@@ -49,7 +50,8 @@ type Controller struct {
RefreshPageFunc func() RefreshPageFunc func()
popUpQueueMutex sync.Mutex popUpQueueMutex sync.Mutex
popUpQueue *widgets.PlayQueueList popUpQueue *widget.PopUp
popUpQueueList *widgets.PlayQueueList
popUpQueueLastUsed int64 popUpQueueLastUsed int64
escapablePopUp *widget.PopUp escapablePopUp *widget.PopUp
haveModal bool haveModal bool
@@ -64,18 +66,22 @@ func New(app *backend.App, appVersion string, mainWindow fyne.Window) *Controlle
} }
c.initVisualizations() c.initVisualizations()
c.App.PlaybackManager.OnQueueChange(func() { c.App.PlaybackManager.OnQueueChange(func() {
c.popUpQueueMutex.Lock()
defer c.popUpQueueMutex.Unlock()
if c.popUpQueue != nil { if c.popUpQueue != nil {
c.popUpQueue.SetItems(c.App.PlaybackManager.GetPlayQueue()) c.popUpQueueList.SetItems(c.App.PlaybackManager.GetPlayQueue())
} }
}) })
c.App.PlaybackManager.OnSongChange(func(track mediaprovider.MediaItem, _ *mediaprovider.Track) { c.App.PlaybackManager.OnSongChange(func(track mediaprovider.MediaItem, _ *mediaprovider.Track) {
c.popUpQueueMutex.Lock()
defer c.popUpQueueMutex.Unlock()
if c.popUpQueue == nil { if c.popUpQueue == nil {
return return
} }
if track == nil { if track == nil {
c.popUpQueue.SetNowPlaying("") c.popUpQueueList.SetNowPlaying("")
} else { } else {
c.popUpQueue.SetNowPlaying(track.Metadata().ID) c.popUpQueueList.SetNowPlaying(track.Metadata().ID)
} }
}) })
return c return c
@@ -115,10 +121,28 @@ func (m *Controller) HaveModal() bool {
func (m *Controller) ShowPopUpPlayQueue() { func (m *Controller) ShowPopUpPlayQueue() {
m.popUpQueueMutex.Lock() m.popUpQueueMutex.Lock()
if m.popUpQueue == nil { if m.popUpQueue == nil {
m.popUpQueue = widgets.NewPlayQueueList(m.App.ImageManager, false) m.popUpQueueList = widgets.NewPlayQueueList(m.App.ImageManager, false)
m.popUpQueue.Reorderable = true m.popUpQueueList.Reorderable = true
m.popUpQueue.SetItems(m.App.PlaybackManager.GetPlayQueue()) m.popUpQueueList.SetItems(m.App.PlaybackManager.GetPlayQueue())
m.ConnectPlayQueuelistActions(m.popUpQueue) m.ConnectPlayQueuelistActions(m.popUpQueueList)
title := widget.NewRichTextWithText(lang.L("Play Queue"))
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
ctr := container.NewBorder(title, nil, nil, nil,
container.NewPadded(m.popUpQueueList),
)
m.popUpQueue = widget.NewPopUp(ctr, m.MainWindow.Canvas())
fynetooltip.AddPopUpToolTipLayer(m.popUpQueue)
container.NewThemeOverride(m.popUpQueue, myTheme.WithColorTransformOverride(
theme.ColorNameOverlayBackground,
func(c color.Color) color.Color {
c_ := c.(color.NRGBA)
c_.A = 245
return c_
},
))
// free popUpQueue if it hasn't been used in awhile // free popUpQueue if it hasn't been used in awhile
go func() { go func() {
@@ -127,7 +151,9 @@ func (m *Controller) ShowPopUpPlayQueue() {
m.popUpQueueMutex.Lock() m.popUpQueueMutex.Lock()
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
if m.popUpQueueLastUsed < now-120_000 /*2 min*/ { if m.popUpQueueLastUsed < now-120_000 /*2 min*/ {
fynetooltip.DestroyPopUpToolTipLayer(m.popUpQueue)
m.popUpQueue = nil m.popUpQueue = nil
m.popUpQueueList = nil
m.popUpQueueLastUsed = 0 m.popUpQueueLastUsed = 0
m.popUpQueueMutex.Unlock() m.popUpQueueMutex.Unlock()
t.Stop() t.Stop()
@@ -138,31 +164,17 @@ func (m *Controller) ShowPopUpPlayQueue() {
}() }()
} }
m.popUpQueueLastUsed = time.Now().UnixMilli() m.popUpQueueLastUsed = time.Now().UnixMilli()
popUpQueue := m.popUpQueue popUpQueueList := m.popUpQueueList
pop := m.popUpQueue
m.popUpQueueMutex.Unlock() m.popUpQueueMutex.Unlock()
npID := "" npID := ""
if np := m.App.PlaybackManager.NowPlaying(); np != nil { if np := m.App.PlaybackManager.NowPlaying(); np != nil {
npID = np.Metadata().ID npID = np.Metadata().ID
} }
popUpQueue.SetNowPlaying(npID) popUpQueueList.SetNowPlaying(npID)
popUpQueue.UnselectAll() popUpQueueList.UnselectAll()
title := widget.NewRichTextWithText(lang.L("Play Queue"))
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
ctr := container.NewBorder(title, nil, nil, nil,
container.NewPadded(m.popUpQueue),
)
pop := widget.NewPopUp(ctr, m.MainWindow.Canvas())
container.NewThemeOverride(pop, myTheme.WithColorTransformOverride(
theme.ColorNameOverlayBackground,
func(c color.Color) color.Color {
c_ := c.(color.NRGBA)
c_.A = 245
return c_
},
))
m.ClosePopUpOnEscape(pop) m.ClosePopUpOnEscape(pop)
minSize := fyne.NewSize(300, 400) minSize := fyne.NewSize(300, 400)
maxSize := fyne.NewSize(800, 1000) maxSize := fyne.NewSize(800, 1000)
@@ -171,7 +183,7 @@ func (m *Controller) ShowPopUpPlayQueue() {
fyne.NewSize(canvasSize.Width*0.4, canvasSize.Height*0.5), fyne.NewSize(canvasSize.Width*0.4, canvasSize.Height*0.5),
)) ))
pop.Resize(size) pop.Resize(size)
popUpQueue.ScrollToNowPlaying() // must come after resize popUpQueueList.ScrollToNowPlaying() // must come after resize
pop.ShowAtPosition(fyne.NewPos( pop.ShowAtPosition(fyne.NewPos(
canvasSize.Width-size.Width-10, canvasSize.Width-size.Width-10,
canvasSize.Height-size.Height-100, canvasSize.Height-size.Height-100,
@@ -528,8 +540,10 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map
} }
dlg.OnPageNeedsRefresh = c.RefreshPageFunc dlg.OnPageNeedsRefresh = c.RefreshPageFunc
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas()) pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
fynetooltip.AddPopUpToolTipLayer(pop)
dlg.OnDismiss = func() { dlg.OnDismiss = func() {
pop.Hide() pop.Hide()
fynetooltip.DestroyPopUpToolTipLayer(pop)
c.doModalClosed() c.doModalClosed()
c.App.SaveConfigFile() c.App.SaveConfigFile()
} }
@@ -694,7 +708,7 @@ func (c *Controller) createShareURL(id string) (*url.URL, error) {
if err != nil { if err != nil {
log.Printf("error creating share URL: %v", err) log.Printf("error creating share URL: %v", err)
c.showError( c.showError(
"Failed to share content. This commonly occurs when the server does not support sharing," + "Failed to share content. This commonly occurs when the server does not support sharing, " +
"or has the feature disabled.\nPlease check the server's settings and try again.", "or has the feature disabled.\nPlease check the server's settings and try again.",
) )
return nil, err return nil, err
+31
View File
@@ -18,6 +18,37 @@ const (
Radios Radios
) )
func (p PageName) String() string {
switch p {
case Album:
return "Album"
case Albums:
return "Albums"
case Artist:
return "Artist"
case Artists:
return "Artists"
case Genre:
return "Genre"
case Genres:
return "Genres"
case Favorites:
return "Favorites"
case NowPlaying:
return "Now Playing"
case Playlist:
return "Playlist"
case Playlists:
return "Playlists"
case Tracks:
return "All Tracks"
case Radios:
return "Internet Radio Stations"
default:
return ""
}
}
type Route struct { type Route struct {
Page PageName Page PageName
Arg string Arg string
+34 -19
View File
@@ -1,11 +1,14 @@
package dialogs package dialogs
import ( import (
"fmt"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
@@ -17,7 +20,8 @@ type GraphicEqualizer struct {
OnChanged func(band int, gain float64) OnChanged func(band int, gain float64)
OnPreampChanged func(gain float64) OnPreampChanged func(gain float64)
container *fyne.Container bandSliders []*eqSlider
container *fyne.Container
} }
func NewGraphicEqualizer(preamp float64, bandFreqs []string, bandGains []float64) *GraphicEqualizer { func NewGraphicEqualizer(preamp float64, bandFreqs []string, bandGains []float64) *GraphicEqualizer {
@@ -36,7 +40,8 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain
layout.NewSpacer(), layout.NewSpacer(),
newCaptionTextSizeLabel("-12", fyne.TextAlignTrailing), newCaptionTextSizeLabel("-12", fyne.TextAlignTrailing),
) )
bandSliders := container.New(layouts.NewGridLayoutWithColumnsAndPadding(len(bands)+2, -16)) g.bandSliders = make([]*eqSlider, len(bands))
bandSlidersCtr := container.New(layouts.NewGridLayoutWithColumnsAndPadding(len(bands)+2, -16))
pre := newCaptionTextSizeLabel("Pre", fyne.TextAlignCenter) pre := newCaptionTextSizeLabel("Pre", fyne.TextAlignCenter)
preampSlider := newEQSlider() preampSlider := newEQSlider()
preampSlider.SetValue(preamp) preampSlider.SetValue(preamp)
@@ -44,24 +49,28 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain
if g.OnPreampChanged != nil { if g.OnPreampChanged != nil {
g.OnPreampChanged(f) g.OnPreampChanged(f)
} }
preampSlider.UpdateToolTip()
} }
bandSliders.Add(container.NewBorder(nil, pre, nil, nil, preampSlider)) preampSlider.UpdateToolTip()
bandSliders.Add(container.NewBorder(nil, widget.NewLabel(""), nil, nil, rng)) bandSlidersCtr.Add(container.NewBorder(nil, pre, nil, nil, preampSlider))
bandSlidersCtr.Add(container.NewBorder(nil, widget.NewLabel(""), nil, nil, rng))
for i, band := range bands { for i, band := range bands {
s := newEQSlider() s := newEQSlider()
if i < len(bandGains) { if i < len(bandGains) {
s.SetValue(bandGains[i]) s.SetValue(bandGains[i])
s.UpdateToolTip()
} }
s.OnChanged = func(i int) func(float64) { _i := i
return func(f float64) { s.OnChanged = func(f float64) {
if g.OnChanged != nil { if g.OnChanged != nil {
g.OnChanged(i, f) g.OnChanged(_i, f)
}
} }
}(i) g.bandSliders[_i].UpdateToolTip()
}
l := newCaptionTextSizeLabel(band, fyne.TextAlignCenter) l := newCaptionTextSizeLabel(band, fyne.TextAlignCenter)
c := container.NewBorder(nil, l, nil, nil, s) c := container.NewBorder(nil, l, nil, nil, s)
bandSliders.Add(c) bandSlidersCtr.Add(c)
g.bandSliders[i] = s
} }
g.container = container.NewStack( g.container = container.NewStack(
container.NewBorder(nil, widget.NewLabel(""), nil, nil, container.NewBorder(nil, widget.NewLabel(""), nil, nil,
@@ -73,7 +82,7 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain
), ),
), ),
), ),
bandSliders, bandSlidersCtr,
) )
} }
@@ -90,23 +99,29 @@ func (g *GraphicEqualizer) CreateRenderer() fyne.WidgetRenderer {
} }
type eqSlider struct { type eqSlider struct {
widget.Slider ttwidget.Slider
tappedAt int64
} }
func newEQSlider() *eqSlider { func newEQSlider() *eqSlider {
s := &eqSlider{ s := &eqSlider{
Slider: widget.Slider{ Slider: ttwidget.Slider{
Orientation: widget.Vertical, Slider: widget.Slider{
Min: -12, Orientation: widget.Vertical,
Max: 12, Min: -12,
Step: 0.1, Max: 12,
Step: 0.1,
},
}, },
} }
s.UpdateToolTip()
s.ExtendBaseWidget(s) s.ExtendBaseWidget(s)
return s return s
} }
func (s *eqSlider) UpdateToolTip() {
s.SetToolTip(fmt.Sprintf("%0.1f dB", s.Value))
}
// We implement our own double tapping so that the Tapped behavior // We implement our own double tapping so that the Tapped behavior
// can be triggered instantly. // can be triggered instantly.
func (s *eqSlider) DoubleTapped(e *fyne.PointEvent) { func (s *eqSlider) DoubleTapped(e *fyne.PointEvent) {
+9 -4
View File
@@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
fynetooltip "github.com/dweymouth/fyne-tooltip"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/res"
@@ -40,7 +41,7 @@ type MainWindow struct {
container *fyne.Container container *fyne.Container
// needs to bes shown/hidden when switching between servers based on whether they support radio // needs to bes shown/hidden when switching between servers based on whether they support radio
radioBtn *widget.Button radioBtn fyne.CanvasObject
} }
func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, app *backend.App) MainWindow { func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, app *backend.App) MainWindow {
@@ -49,6 +50,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
Window: fyneApp.NewWindow(displayAppName), Window: fyneApp.NewWindow(displayAppName),
theme: theme.NewMyTheme(&app.Config.Theme, app.ThemesDir()), theme: theme.NewMyTheme(&app.Config.Theme, app.ThemesDir()),
} }
fynetooltip.SetToolTipTextSizeName(theme.SizeNameSubText)
m.theme.NormalFont = app.Config.Application.FontNormalTTF m.theme.NormalFont = app.Config.Application.FontNormalTTF
m.theme.BoldFont = app.Config.Application.FontBoldTTF m.theme.BoldFont = app.Config.Application.FontBoldTTF
@@ -80,7 +82,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller) m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller)
m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane) m.container = container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane)
m.Window.SetContent(m.container) m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.container, m.Window.Canvas()))
m.setInitialSize() m.setInitialSize()
app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) { app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
if item == nil { if item == nil {
@@ -191,8 +193,11 @@ func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName
} }
_, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider) _, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider)
m.radioBtn.Hidden = !supportsRadio if supportsRadio {
m.radioBtn.Refresh() m.radioBtn.Show()
} else {
m.radioBtn.Hide()
}
m.App.SaveConfigFile() m.App.SaveConfigFile()
+16 -4
View File
@@ -205,11 +205,23 @@ func (m *MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
// Returns a map [themeFileName] -> displayName // Returns a map [themeFileName] -> displayName
func (m *MyTheme) ListThemeFiles() map[string]string { func (m *MyTheme) ListThemeFiles() map[string]string {
files, _ := filepath.Glob(m.themeFileDir + "/*.toml") // Use filepath.Join to create a cross-platform file path
pattern := filepath.Join(m.themeFileDir, "/*.toml")
files, err := filepath.Glob(pattern)
if err != nil {
log.Printf("Failed to glob files: %v", err)
}
result := make(map[string]string) result := make(map[string]string)
for _, filepath := range files { for _, filePath := range files {
if themeFile, err := ReadThemeFile(filepath); err == nil { // Clean the path to avoid issues with slashes
result[path.Base(filepath)] = themeFile.SupersonicTheme.Name cleanPath := filepath.Clean(filePath)
// Now read the theme file, using the cleaned path
if themeFile, err := ReadThemeFile(cleanPath); err == nil {
result[filepath.Base(cleanPath)] = themeFile.SupersonicTheme.Name
} else {
log.Printf("Failed to load theme file: %s, error: %v", cleanPath, err)
} }
} }
return result return result
+22
View File
@@ -12,10 +12,12 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
@@ -252,6 +254,18 @@ func NewTruncatingLabel() *widget.Label {
return rt return rt
} }
func NewTruncatingTooltipRichText() *ttwidget.RichText {
rt := ttwidget.NewRichTextWithText("")
rt.Truncation = fyne.TextTruncateEllipsis
return rt
}
func NewTruncatingTooltipLabel() *ttwidget.Label {
rt := ttwidget.NewLabel("")
rt.Truncation = fyne.TextTruncateEllipsis
return rt
}
func NewTrailingAlignLabel() *widget.Label { func NewTrailingAlignLabel() *widget.Label {
rt := widget.NewLabel("") rt := widget.NewLabel("")
rt.Alignment = fyne.TextAlignTrailing rt.Alignment = fyne.TextAlignTrailing
@@ -269,6 +283,14 @@ func LocalizeSlice(s []string) []string {
return sharedutil.MapSlice(s, func(s string) string { return lang.L(s) }) return sharedutil.MapSlice(s, func(s string) string { return lang.L(s) })
} }
type ToolTipRichText struct {
ttwidget.RichText
OnMouseIn func(e *desktop.MouseEvent)
OnMouseOut func()
OnTapped func(e *fyne.PointEvent)
}
type HSpace struct { type HSpace struct {
widget.BaseWidget widget.BaseWidget
+8 -4
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
@@ -22,7 +23,7 @@ import (
) )
type AlbumFilterButton struct { type AlbumFilterButton struct {
widget.Button ttwidget.Button
OnChanged func() OnChanged func()
GenreDisabled bool GenreDisabled bool
@@ -37,10 +38,13 @@ type AlbumFilterButton struct {
func NewAlbumFilterButton(filter mediaprovider.AlbumFilter, fetchGenresFunc func() ([]*mediaprovider.Genre, error)) *AlbumFilterButton { func NewAlbumFilterButton(filter mediaprovider.AlbumFilter, fetchGenresFunc func() ([]*mediaprovider.Genre, error)) *AlbumFilterButton {
a := &AlbumFilterButton{ a := &AlbumFilterButton{
filter: filter, filter: filter,
Button: widget.Button{ Button: ttwidget.Button{
Icon: theme.NewThemedResource(myTheme.FilterIcon), Button: widget.Button{
Icon: theme.NewThemedResource(myTheme.FilterIcon),
},
}, },
} }
a.SetToolTip(lang.L("Filter albums"))
a.OnTapped = a.showFilterDialog a.OnTapped = a.showFilterDialog
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.genreListChan = make(chan []string) a.genreListChan = make(chan []string)
@@ -189,7 +193,7 @@ func NewAlbumFilterPopup(filter *AlbumFilterButton) *AlbumFilterPopup {
title.TextStyle.Bold = true title.TextStyle.Bold = true
a.container = container.NewVBox( a.container = container.NewVBox(
container.NewHBox(layout.NewSpacer(), title, layout.NewSpacer()), container.NewHBox(layout.NewSpacer(), title, layout.NewSpacer()),
container.NewHBox(widget.NewLabel(lang.L("Year from")), minYear, widget.NewLabel("to"), maxYear), container.NewHBox(widget.NewLabel(lang.L("Year from")), minYear, widget.NewLabel(lang.L("to")), maxYear),
container.NewHBox(a.isFavorite, a.isNotFavorite), container.NewHBox(a.isFavorite, a.isNotFavorite),
a.genreFilter, a.genreFilter,
) )
+4
View File
@@ -3,6 +3,7 @@ package widgets
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -31,7 +32,9 @@ func NewAuxControls(initialVolume int) *AuxControls {
showQueue: NewIconButton(myTheme.PlayQueueIcon, nil), showQueue: NewIconButton(myTheme.PlayQueueIcon, nil),
} }
a.loop.IconSize = IconButtonSizeSmaller a.loop.IconSize = IconButtonSizeSmaller
a.loop.SetToolTip(lang.L("Shuffle"))
a.showQueue.IconSize = IconButtonSizeSmaller a.showQueue.IconSize = IconButtonSizeSmaller
a.showQueue.SetToolTip(lang.L("Show play queue"))
a.container = container.NewHBox( a.container = container.NewHBox(
layout.NewSpacer(), layout.NewSpacer(),
container.NewVBox( container.NewVBox(
@@ -125,6 +128,7 @@ func NewVolumeControl(initialVol int) *VolumeControl {
v := &VolumeControl{} v := &VolumeControl{}
v.ExtendBaseWidget(v) v.ExtendBaseWidget(v)
v.icon = NewIconButton(theme.VolumeUpIcon(), v.toggleMute) v.icon = NewIconButton(theme.VolumeUpIcon(), v.toggleMute)
v.icon.SetToolTip(lang.L("Mute"))
v.icon.IconSize = IconButtonSizeSmaller v.icon.IconSize = IconButtonSizeSmaller
v.slider = NewVolumeSlider(100) v.slider = NewVolumeSlider(100)
v.lastVol = initialVol v.lastVol = initialVol
+51 -19
View File
@@ -1,12 +1,14 @@
package widgets package widgets
import ( import (
"image/color"
"sync" "sync"
"time" "time"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -72,6 +74,7 @@ func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) {
var _ fyne.Tappable = (*FocusListRowBase)(nil) var _ fyne.Tappable = (*FocusListRowBase)(nil)
var _ fyne.Widget = (*FocusListRowBase)(nil) var _ fyne.Widget = (*FocusListRowBase)(nil)
var _ fyne.Focusable = (*FocusListRowBase)(nil) var _ fyne.Focusable = (*FocusListRowBase)(nil)
var _ desktop.Hoverable = (*FocusListRowBase)(nil)
// Base type used for all list rows in widgets such as Tracklist, etc. // Base type used for all list rows in widgets such as Tracklist, etc.
type FocusListRowBase struct { type FocusListRowBase struct {
@@ -81,14 +84,16 @@ type FocusListRowBase struct {
Content fyne.CanvasObject Content fyne.CanvasObject
Selected bool Selected bool
Focused bool Focused bool
hovered bool
OnTapped func() OnTapped func()
OnDoubleTapped func() OnDoubleTapped func()
OnFocusNeighbor func(up bool) //TODO: func(up, selecting bool) OnFocusNeighbor func(up bool) //TODO: func(up, selecting bool)
tappedAt int64 // unixMillis tappedAt int64 // unixMillis
focusedRect *canvas.Rectangle focusedRect canvas.Rectangle
selectionRect *canvas.Rectangle selectionRect canvas.Rectangle
hoverRect canvas.Rectangle
} }
func (l *FocusListRowBase) SetOnTapped(f func()) { func (l *FocusListRowBase) SetOnTapped(f func()) {
@@ -139,12 +144,14 @@ func (l *FocusListRowBase) Tapped(*fyne.PointEvent) {
func (l *FocusListRowBase) FocusGained() { func (l *FocusListRowBase) FocusGained() {
l.Focused = true l.Focused = true
l.Refresh() l.focusedRect.FillColor = theme.HoverColor()
l.focusedRect.Refresh()
} }
func (l *FocusListRowBase) FocusLost() { func (l *FocusListRowBase) FocusLost() {
l.Focused = false l.Focused = false
l.Refresh() l.focusedRect.FillColor = color.Transparent
l.focusedRect.Refresh()
} }
func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) { func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) {
@@ -180,26 +187,51 @@ func (l *FocusListRowBase) TypedKey(e *fyne.KeyEvent) {
func (l *FocusListRowBase) TypedRune(r rune) { func (l *FocusListRowBase) TypedRune(r rune) {
} }
func (l *FocusListRowBase) MouseIn(e *desktop.MouseEvent) {
l.hovered = true
l.hoverRect.FillColor = theme.HoverColor()
l.hoverRect.Refresh()
}
func (l *FocusListRowBase) MouseMoved(e *desktop.MouseEvent) {
}
func (l *FocusListRowBase) MouseOut() {
l.hovered = false
l.hoverRect.FillColor = color.Transparent
l.hoverRect.Refresh()
}
func (l *FocusListRowBase) Refresh() { func (l *FocusListRowBase) Refresh() {
l.focusedRect.FillColor = theme.HoverColor() l.updateBackgroundRendering()
l.focusedRect.Hidden = !l.Focused
l.selectionRect.FillColor = theme.SelectionColor()
l.selectionRect.Hidden = !l.Selected
l.BaseWidget.Refresh() l.BaseWidget.Refresh()
} }
func (l *FocusListRowBase) updateBackgroundRendering() {
if l.Selected {
l.selectionRect.FillColor = theme.SelectionColor()
} else {
l.selectionRect.FillColor = color.Transparent
}
if l.Focused {
l.focusedRect.FillColor = theme.HoverColor()
} else {
l.focusedRect.FillColor = color.Transparent
}
if l.hovered {
l.hoverRect.FillColor = theme.HoverColor()
} else {
l.hoverRect.FillColor = color.Transparent
}
}
func (l *FocusListRowBase) CreateRenderer() fyne.WidgetRenderer { func (l *FocusListRowBase) CreateRenderer() fyne.WidgetRenderer {
if l.selectionRect == nil { l.selectionRect.CornerRadius = theme.SelectionRadiusSize()
l.selectionRect = canvas.NewRectangle(theme.SelectionColor()) l.focusedRect.CornerRadius = theme.SelectionRadiusSize()
l.selectionRect.CornerRadius = theme.SelectionRadiusSize() l.hoverRect.CornerRadius = theme.SelectionRadiusSize()
l.selectionRect.Hidden = !l.Selected l.updateBackgroundRendering()
}
if l.focusedRect == nil {
l.focusedRect = canvas.NewRectangle(theme.HoverColor())
l.focusedRect.CornerRadius = theme.SelectionRadiusSize()
l.focusedRect.Hidden = !l.Focused
}
return widget.NewSimpleRenderer( return widget.NewSimpleRenderer(
container.NewStack(l.selectionRect, l.focusedRect, l.Content), container.NewStack(&l.selectionRect, &l.focusedRect, &l.hoverRect, l.Content),
) )
} }
+4 -2
View File
@@ -13,6 +13,7 @@ import (
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/res"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
@@ -142,7 +143,7 @@ type GridViewItem struct {
itemID string itemID string
secondaryIDs []string secondaryIDs []string
primaryText *widget.Hyperlink primaryText *ttwidget.Hyperlink
secondaryText *MultiHyperlink secondaryText *MultiHyperlink
suffix string suffix string
container *fyne.Container container *fyne.Container
@@ -165,7 +166,7 @@ type GridViewItem struct {
func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem { func NewGridViewItem(placeholderResource fyne.Resource) *GridViewItem {
g := &GridViewItem{ g := &GridViewItem{
primaryText: widget.NewHyperlink("", nil), primaryText: ttwidget.NewHyperlink("", nil),
secondaryText: NewMultiHyperlink(), secondaryText: NewMultiHyperlink(),
Cover: newCoverImage(placeholderResource), Cover: newCoverImage(placeholderResource),
} }
@@ -220,6 +221,7 @@ func (g *GridViewItem) Update(model GridViewItemModel) {
g.itemID = model.ID g.itemID = model.ID
g.secondaryIDs = model.SecondaryIDs g.secondaryIDs = model.SecondaryIDs
g.primaryText.SetText(model.Name) g.primaryText.SetText(model.Name)
g.primaryText.SetToolTip(model.Name)
g.secondaryText.BuildSegments(model.Secondary, model.SecondaryIDs) g.secondaryText.BuildSegments(model.Secondary, model.SecondaryIDs)
if g.ShowSuffix { if g.ShowSuffix {
g.secondaryText.Suffix = model.Suffix g.secondaryText.Suffix = model.Suffix
+7 -3
View File
@@ -7,6 +7,7 @@ import (
"fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
) )
@@ -20,7 +21,7 @@ const (
) )
type IconButton struct { type IconButton struct {
widget.BaseWidget ttwidget.ToolTipWidget
Highlighted bool Highlighted bool
IconSize IconButtonSize IconSize IconButtonSize
@@ -84,7 +85,8 @@ func (i *IconButton) TypedKey(e *fyne.KeyEvent) {
func (i *IconButton) TypedRune(r rune) { func (i *IconButton) TypedRune(r rune) {
} }
func (i *IconButton) MouseIn(*desktop.MouseEvent) { func (i *IconButton) MouseIn(e *desktop.MouseEvent) {
i.ToolTipWidget.MouseIn(e)
if !i.hovered { if !i.hovered {
defer i.Refresh() defer i.Refresh()
} }
@@ -92,13 +94,15 @@ func (i *IconButton) MouseIn(*desktop.MouseEvent) {
} }
func (i *IconButton) MouseOut() { func (i *IconButton) MouseOut() {
i.ToolTipWidget.MouseOut()
if i.hovered { if i.hovered {
defer i.Refresh() defer i.Refresh()
} }
i.hovered = false i.hovered = false
} }
func (i *IconButton) MouseMoved(*desktop.MouseEvent) { func (i *IconButton) MouseMoved(e *desktop.MouseEvent) {
i.ToolTipWidget.MouseMoved(e)
} }
func (i *IconButton) MinSize() fyne.Size { func (i *IconButton) MinSize() fyne.Size {
+31 -7
View File
@@ -3,8 +3,10 @@ package widgets
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
) )
type MultiHyperlink struct { type MultiHyperlink struct {
@@ -16,7 +18,9 @@ type MultiHyperlink struct {
// only if there is enough room // only if there is enough room
Suffix string Suffix string
OnTapped func(string) OnMouseIn func(*desktop.MouseEvent)
OnMouseOut func()
OnTapped func(string)
SizeName fyne.ThemeSizeName SizeName fyne.ThemeSizeName
SuffixSizeName fyne.ThemeSizeName SuffixSizeName fyne.ThemeSizeName
@@ -30,7 +34,7 @@ type MultiHyperlink struct {
//provider *widget.RichText //provider *widget.RichText
objects []fyne.CanvasObject objects []fyne.CanvasObject
suffixLabel *widget.RichText suffixLabel *ttwidget.RichText
content *fyne.Container content *fyne.Container
} }
@@ -136,7 +140,9 @@ func (c *MultiHyperlink) layoutObjects() {
c.content.Objects = c.objects[:2*i-1] c.content.Objects = c.objects[:2*i-1]
if i == len(c.Segments) && c.Suffix != "" { if i == len(c.Segments) && c.Suffix != "" {
if c.suffixLabel == nil { if c.suffixLabel == nil {
c.suffixLabel = widget.NewRichTextWithText("· " + c.Suffix) c.suffixLabel = ttwidget.NewRichTextWithText("· " + c.Suffix)
c.suffixLabel.OnMouseIn = c.callOnMouseIn
c.suffixLabel.OnMouseOut = c.callOnMouseOut
} else { } else {
c.suffixLabel.Segments[0].(*widget.TextSegment).Text = "· " + c.Suffix c.suffixLabel.Segments[0].(*widget.TextSegment).Text = "· " + c.Suffix
} }
@@ -157,14 +163,18 @@ func (c *MultiHyperlink) layoutObjects() {
func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string) fyne.CanvasObject { func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string) fyne.CanvasObject {
if obj != nil { if obj != nil {
if label, ok := obj.(*widget.RichText); ok { if label, ok := obj.(*ttwidget.RichText); ok {
ts := label.Segments[0].(*widget.TextSegment) ts := label.Segments[0].(*widget.TextSegment)
ts.Text = text ts.Text = text
ts.Style.SizeName = c.sizeName() ts.Style.SizeName = c.sizeName()
label.SetToolTip(text)
return label return label
} }
} }
l := widget.NewRichTextWithText(text) l := ttwidget.NewRichTextWithText(text)
l.SetToolTip(text)
l.OnMouseIn = c.callOnMouseIn
l.OnMouseOut = c.callOnMouseOut
l.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName() l.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName()
l.Truncation = fyne.TextTruncateEllipsis l.Truncation = fyne.TextTruncateEllipsis
return l return l
@@ -172,20 +182,34 @@ func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string
func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text, link string) fyne.CanvasObject { func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text, link string) fyne.CanvasObject {
if obj != nil { if obj != nil {
if l, ok := obj.(*widget.Hyperlink); ok { if l, ok := obj.(*ttwidget.Hyperlink); ok {
l.Text = text l.Text = text
l.SetToolTip(text)
l.SizeName = c.sizeName() l.SizeName = c.sizeName()
l.OnTapped = func() { c.onSegmentTapped(link) } l.OnTapped = func() { c.onSegmentTapped(link) }
return l return l
} }
} }
l := widget.NewHyperlink(text, nil) l := ttwidget.NewHyperlink(text, nil)
l.SetToolTip(text)
l.SizeName = c.sizeName() l.SizeName = c.sizeName()
l.Truncation = fyne.TextTruncateEllipsis l.Truncation = fyne.TextTruncateEllipsis
l.OnTapped = func() { c.onSegmentTapped(link) } l.OnTapped = func() { c.onSegmentTapped(link) }
return l return l
} }
func (c *MultiHyperlink) callOnMouseIn(e *desktop.MouseEvent) {
if f := c.OnMouseIn; f != nil {
f(e)
}
}
func (c *MultiHyperlink) callOnMouseOut() {
if f := c.OnMouseOut; f != nil {
f()
}
}
func (c *MultiHyperlink) newSeparatorLabel() *widget.RichText { func (c *MultiHyperlink) newSeparatorLabel() *widget.RichText {
rt := widget.NewRichTextWithText(", ") rt := widget.NewRichTextWithText(", ")
rt.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName() rt.Segments[0].(*widget.TextSegment).Style.SizeName = c.sizeName()
+8 -4
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
) )
// Shows the current album art, track name, artist name, and album name // Shows the current album art, track name, artist name, and album name
@@ -26,7 +27,7 @@ type NowPlayingCard struct {
trackName *OptionHyperlink trackName *OptionHyperlink
artistName *MultiHyperlink artistName *MultiHyperlink
albumName *widget.Hyperlink albumName *ttwidget.Hyperlink
cover *ImagePlaceholder cover *ImagePlaceholder
menu *widget.PopUpMenu menu *widget.PopUpMenu
ratingMenu *fyne.MenuItem ratingMenu *fyne.MenuItem
@@ -46,7 +47,7 @@ func NewNowPlayingCard() *NowPlayingCard {
n := &NowPlayingCard{ n := &NowPlayingCard{
trackName: NewOptionHyperlink(), trackName: NewOptionHyperlink(),
artistName: NewMultiHyperlink(), artistName: NewMultiHyperlink(),
albumName: widget.NewHyperlink("", nil), albumName: ttwidget.NewHyperlink("", nil),
} }
n.ExtendBaseWidget(n) n.ExtendBaseWidget(n)
n.cover = NewImagePlaceholder(myTheme.TracksIcon, 85) n.cover = NewImagePlaceholder(myTheme.TracksIcon, 85)
@@ -135,20 +136,23 @@ func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer {
func (n *NowPlayingCard) Update(track mediaprovider.MediaItem) { func (n *NowPlayingCard) Update(track mediaprovider.MediaItem) {
if track == nil { if track == nil {
n.trackName.SetText("") n.trackName.SetTextAndToolTip("")
n.artistName.BuildSegments([]string{}, []string{}) n.artistName.BuildSegments([]string{}, []string{})
n.albumName.SetText("") n.albumName.SetText("")
n.albumName.SetToolTip("")
n.cover.Hidden = true n.cover.Hidden = true
} else { } else {
n.cover.Hidden = false n.cover.Hidden = false
n.trackName.SetText(track.Metadata().Name) n.trackName.SetTextAndToolTip(track.Metadata().Name)
if tr, ok := track.(*mediaprovider.Track); ok { if tr, ok := track.(*mediaprovider.Track); ok {
n.artistName.BuildSegments(tr.ArtistNames, tr.ArtistIDs) n.artistName.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
n.albumName.SetText(tr.Album) n.albumName.SetText(tr.Album)
n.albumName.SetToolTip(tr.Album)
n.cover.PlaceholderIcon = myTheme.TracksIcon n.cover.PlaceholderIcon = myTheme.TracksIcon
} else { } else {
n.artistName.BuildSegments([]string{}, []string{}) n.artistName.BuildSegments([]string{}, []string{})
n.albumName.SetText("") n.albumName.SetText("")
n.albumName.SetToolTip("")
n.cover.PlaceholderIcon = myTheme.RadioIcon n.cover.PlaceholderIcon = myTheme.RadioIcon
} }
} }
+5 -3
View File
@@ -5,13 +5,14 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
) )
// OptionHyperlink is a widget that displays a "vertical dots"/"more" button // OptionHyperlink is a widget that displays a "vertical dots"/"more" button
// to the right of the text, which can show a menu. // to the right of the text, which can show a menu.
type OptionHyperlink struct { type OptionHyperlink struct {
widget.BaseWidget widget.BaseWidget
h *widget.Hyperlink h *ttwidget.Hyperlink
b *IconButton b *IconButton
OnShowMenu func(btnPos fyne.Position) OnShowMenu func(btnPos fyne.Position)
@@ -21,7 +22,7 @@ type OptionHyperlink struct {
func NewOptionHyperlink() *OptionHyperlink { func NewOptionHyperlink() *OptionHyperlink {
o := &OptionHyperlink{ o := &OptionHyperlink{
h: widget.NewHyperlink("", nil), h: ttwidget.NewHyperlink("", nil),
} }
o.h.Truncation = fyne.TextTruncateEllipsis o.h.Truncation = fyne.TextTruncateEllipsis
o.b = NewIconButton(theme.MoreVerticalIcon(), func() { o.b = NewIconButton(theme.MoreVerticalIcon(), func() {
@@ -34,8 +35,9 @@ func NewOptionHyperlink() *OptionHyperlink {
return o return o
} }
func (o *OptionHyperlink) SetText(text string) { func (o *OptionHyperlink) SetTextAndToolTip(text string) {
o.h.SetText(text) o.h.SetText(text)
o.h.SetToolTip(text)
o.updatePreferredWidth() o.updatePreferredWidth()
o.BaseWidget.Refresh() o.BaseWidget.Refresh()
} }
+6
View File
@@ -5,6 +5,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -128,8 +129,11 @@ func NewPlayerControls() *PlayerControls {
} }
pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {}) pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {})
pc.prev.SetToolTip(lang.L("Previous"))
pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {}) pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {})
pc.next.SetToolTip(lang.L("Next"))
pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {}) pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {})
pc.playpause.SetToolTip(lang.L("Play"))
pc.playpause.IconSize = IconButtonSizeBigger pc.playpause.IconSize = IconButtonSizeBigger
buttons := container.NewHBox(layout.NewSpacer(), pc.prev, pc.playpause, pc.next, layout.NewSpacer()) buttons := container.NewHBox(layout.NewSpacer(), pc.prev, pc.playpause, pc.next, layout.NewSpacer())
@@ -165,8 +169,10 @@ func (pc *PlayerControls) OnPlayPause(f func()) {
func (pc *PlayerControls) SetPlaying(playing bool) { func (pc *PlayerControls) SetPlaying(playing bool) {
if playing { if playing {
pc.playpause.SetIcon(theme.MediaPauseIcon()) pc.playpause.SetIcon(theme.MediaPauseIcon())
pc.playpause.SetToolTip(lang.L("Pause"))
} else { } else {
pc.playpause.SetIcon(theme.MediaPlayIcon()) pc.playpause.SetIcon(theme.MediaPlayIcon())
pc.playpause.SetToolTip(lang.L("Play"))
} }
} }
+13 -10
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
@@ -436,7 +437,7 @@ type PlayQueueListRow struct {
playingIcon fyne.CanvasObject playingIcon fyne.CanvasObject
num *widget.Label num *widget.Label
cover *ImagePlaceholder cover *ImagePlaceholder
title *widget.Label title *ttwidget.Label
artist *MultiHyperlink artist *MultiHyperlink
time *widget.Label time *widget.Label
} }
@@ -447,14 +448,18 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager,
playQueueList: playQueueList, playQueueList: playQueueList,
num: widget.NewLabel(""), num: widget.NewLabel(""),
cover: NewImagePlaceholder(myTheme.TracksIcon, playQueueListThumbnailSize), cover: NewImagePlaceholder(myTheme.TracksIcon, playQueueListThumbnailSize),
title: util.NewTruncatingLabel(), title: util.NewTruncatingTooltipLabel(),
artist: NewMultiHyperlink(), artist: NewMultiHyperlink(),
time: util.NewTrailingAlignLabel(), time: util.NewTrailingAlignLabel(),
} }
p.ExtendBaseWidget(p) p.ExtendBaseWidget(p)
p.cover.ScaleMode = canvas.ImageScaleFastest p.cover.ScaleMode = canvas.ImageScaleFastest
p.title.OnMouseIn = p.MouseIn
p.title.OnMouseOut = p.MouseOut
p.artist.OnTapped = playQueueList.onArtistTapped p.artist.OnTapped = playQueueList.onArtistTapped
p.artist.OnMouseIn = p.MouseIn
p.artist.OnMouseOut = p.MouseOut
p.OnDoubleTapped = func() { p.OnDoubleTapped = func() {
playQueueList.onPlayTrackAt(p.ItemID()) playQueueList.onPlayTrackAt(p.ItemID())
} }
@@ -490,15 +495,12 @@ func (p *PlayQueueListRow) TappedSecondary(e *fyne.PointEvent) {
} }
func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) { func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
changed := false
if tm.Selected != p.Selected { if tm.Selected != p.Selected {
p.Selected = tm.Selected p.Selected = tm.Selected
changed = true
} }
if num := strconv.Itoa(rowNum); p.num.Text != num { if num := strconv.Itoa(rowNum); p.num.Text != num {
p.num.Text = num p.num.Text = num
changed = true
} }
// Update info that can change if this row is bound to // Update info that can change if this row is bound to
@@ -514,9 +516,9 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
p.EnsureUnfocused() p.EnsureUnfocused()
p.trackID = meta.ID p.trackID = meta.ID
p.title.Text = meta.Name p.title.Text = meta.Name
p.title.SetToolTip(meta.Name)
p.artist.BuildSegments(meta.Artists, meta.ArtistIDs) p.artist.BuildSegments(meta.Artists, meta.ArtistIDs)
p.time.Text = util.SecondsToMMSS(float64(meta.Duration)) p.time.Text = util.SecondsToMMSS(float64(meta.Duration))
changed = true
} }
// Render whether track is playing or not // Render whether track is playing or not
@@ -529,10 +531,11 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
} else { } else {
p.Content.(*fyne.Container).Objects[0] = container.NewCenter(p.num) p.Content.(*fyne.Container).Objects[0] = container.NewCenter(p.num)
} }
changed = true
} }
if changed { // we always need to refresh in case of light/dark change
p.Refresh() // even if no info changed in the update, since the
} // PlayQueueList is used in the pop up queue which may be
// hidden and re-shown after a theme variant change
p.Refresh()
} }
+22 -6
View File
@@ -13,6 +13,7 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
ttwidget "github.com/dweymouth/fyne-tooltip/widget"
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
@@ -151,7 +152,7 @@ type tracklistRowBase struct {
playCount int playCount int
num *widget.Label num *widget.Label
name *widget.RichText // for bold support name *ttwidget.RichText
artist *MultiHyperlink artist *MultiHyperlink
album *MultiHyperlink // for disabled support, if albumID is "" album *MultiHyperlink // for disabled support, if albumID is ""
composer *MultiHyperlink composer *MultiHyperlink
@@ -161,9 +162,9 @@ type tracklistRowBase struct {
rating *StarRating rating *StarRating
bitrate *widget.Label bitrate *widget.Label
plays *widget.Label plays *widget.Label
comment *widget.Label comment *ttwidget.Label
size *widget.Label size *widget.Label
path *widget.Label path *ttwidget.Label
// must be injected by extending widget // must be injected by extending widget
setColVisibility func(int, bool) bool setColVisibility func(int, bool) bool
@@ -274,13 +275,21 @@ func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject)
func (t *tracklistRowBase) create(tracklist *Tracklist) { func (t *tracklistRowBase) create(tracklist *Tracklist) {
t.tracklist = tracklist t.tracklist = tracklist
t.num = util.NewTrailingAlignLabel() t.num = util.NewTrailingAlignLabel()
t.name = util.NewTruncatingRichText() t.name = util.NewTruncatingTooltipRichText()
t.name.OnMouseIn = t.MouseIn
t.name.OnMouseOut = t.MouseOut
t.artist = NewMultiHyperlink() t.artist = NewMultiHyperlink()
t.artist.OnTapped = tracklist.onArtistTapped t.artist.OnTapped = tracklist.onArtistTapped
t.artist.OnMouseIn = t.MouseIn
t.artist.OnMouseOut = t.MouseOut
t.album = NewMultiHyperlink() t.album = NewMultiHyperlink()
t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) } t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) }
t.album.OnMouseIn = t.MouseIn
t.album.OnMouseOut = t.MouseOut
t.composer = NewMultiHyperlink() t.composer = NewMultiHyperlink()
t.composer.OnTapped = func(id string) { tracklist.onArtistTapped(id) } t.composer.OnTapped = func(id string) { tracklist.onArtistTapped(id) }
t.composer.OnMouseIn = t.MouseIn
t.composer.OnMouseOut = t.MouseOut
t.dur = util.NewTrailingAlignLabel() t.dur = util.NewTrailingAlignLabel()
t.year = util.NewTrailingAlignLabel() t.year = util.NewTrailingAlignLabel()
favorite := NewFavoriteIcon() favorite := NewFavoriteIcon()
@@ -291,10 +300,14 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) {
t.rating.StarSize = 16 t.rating.StarSize = 16
t.rating.OnRatingChanged = t.setTrackRating t.rating.OnRatingChanged = t.setTrackRating
t.plays = util.NewTrailingAlignLabel() t.plays = util.NewTrailingAlignLabel()
t.comment = util.NewTruncatingLabel() t.comment = util.NewTruncatingTooltipLabel()
t.comment.OnMouseIn = t.MouseIn
t.comment.OnMouseOut = t.MouseOut
t.bitrate = util.NewTrailingAlignLabel() t.bitrate = util.NewTrailingAlignLabel()
t.size = util.NewTrailingAlignLabel() t.size = util.NewTrailingAlignLabel()
t.path = util.NewTruncatingLabel() t.path = util.NewTruncatingTooltipLabel()
t.path.OnMouseIn = t.MouseIn
t.path.OnMouseOut = t.MouseOut
} }
func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) { func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) {
@@ -319,6 +332,7 @@ func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int) {
t.trackID = id t.trackID = id
t.name.Segments[0].(*widget.TextSegment).Text = tr.Title t.name.Segments[0].(*widget.TextSegment).Text = tr.Title
t.name.SetToolTip(tr.Title)
t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs) t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID}) t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID})
t.composer.BuildSegments(tr.ComposerNames, tr.ComposerIDs) t.composer.BuildSegments(tr.ComposerNames, tr.ComposerIDs)
@@ -326,9 +340,11 @@ func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int) {
t.year.Text = strconv.Itoa(tr.Year) t.year.Text = strconv.Itoa(tr.Year)
t.plays.Text = strconv.Itoa(int(tr.PlayCount)) t.plays.Text = strconv.Itoa(int(tr.PlayCount))
t.comment.Text = tr.Comment t.comment.Text = tr.Comment
t.comment.SetToolTip(tr.Comment)
t.bitrate.Text = strconv.Itoa(tr.BitRate) t.bitrate.Text = strconv.Itoa(tr.BitRate)
t.size.Text = util.BytesToSizeString(tr.Size) t.size.Text = util.BytesToSizeString(tr.Size)
t.path.Text = tr.FilePath t.path.Text = tr.FilePath
t.path.SetToolTip(tr.FilePath)
changed = true changed = true
} }