diff --git a/libremsonic/app.py b/libremsonic/app.py index d8830cf..df97858 100644 --- a/libremsonic/app.py +++ b/libremsonic/app.py @@ -89,6 +89,9 @@ class LibremsonicApp(Gtk.Application): add_action('prev-track', self.on_prev_track) add_action('repeat-press', self.on_repeat_press) add_action('shuffle-press', self.on_shuffle_press) + add_action('play-queue-click', + self.on_play_queue_click, + parameter_type='s') # Navigation actions. add_action('play-next', self.on_play_next, parameter_type='as') @@ -301,6 +304,9 @@ class LibremsonicApp(Gtk.Application): self.state.shuffle_on = not self.state.shuffle_on self.update_window() + def on_play_queue_click(self, action, song_id): + self.play_song(song_id.get_string(), reset=True) + def on_play_next(self, action, song_ids): if self.state.current_song is None: insert_at = 0 diff --git a/libremsonic/ui/app_styles.css b/libremsonic/ui/app_styles.css index 50eda3c..0417d76 100644 --- a/libremsonic/ui/app_styles.css +++ b/libremsonic/ui/app_styles.css @@ -118,6 +118,16 @@ margin: 10px; } +#play-queue-playing-icon { + min-height: 40px; + min-width: 40px; +} + +#play-queue-row-image { + min-height: 50px; + min-width: 50px; +} + /* General */ .menu-button { padding: 5px; diff --git a/libremsonic/ui/common/spinner_image.py b/libremsonic/ui/common/spinner_image.py index 7dee439..0f08901 100644 --- a/libremsonic/ui/common/spinner_image.py +++ b/libremsonic/ui/common/spinner_image.py @@ -4,10 +4,16 @@ from gi.repository import Gtk class SpinnerImage(Gtk.Overlay): - def __init__(self, loading=True, image_name=None, spinner_name=None): + def __init__( + self, + loading=True, + image_name=None, + spinner_name=None, + **kwargs, + ): Gtk.Overlay.__init__(self) - self.image = Gtk.Image(name=image_name) + self.image = Gtk.Image(name=image_name, **kwargs) self.add(self.image) self.spinner = Gtk.Spinner( diff --git a/libremsonic/ui/player_controls.py b/libremsonic/ui/player_controls.py index e095813..27ba3f0 100644 --- a/libremsonic/ui/player_controls.py +++ b/libremsonic/ui/player_controls.py @@ -35,13 +35,16 @@ class PlayerControls(Gtk.ActionBar): ), } editing: bool = False + current_song = None class PlayQueueSong(GObject.GObject): song_id = GObject.property(type=str) + playing = GObject.property(type=str) - def __init__(self, song_id: str): + def __init__(self, song_id: str, playing: bool): GObject.GObject.__init__(self) self.song_id = song_id + self.playing = str(playing) def __init__(self): Gtk.ActionBar.__init__(self) @@ -134,7 +137,8 @@ class PlayerControls(Gtk.ActionBar): f'Play Queue: {play_queue_len} {song_label}') new_model = [ - PlayerControls.PlayQueueSong(s) for s in state.play_queue + PlayerControls.PlayQueueSong(s, s == state.current_song.id) + for s in state.play_queue ] util.diff_model_store(self.play_queue_store, new_model) @@ -165,6 +169,7 @@ class PlayerControls(Gtk.ActionBar): def on_play_queue_click(self, button): self.play_queue_popover.set_relative_to(button) + # TODO scroll the sfcurrently playing song into view. self.play_queue_popover.popup() self.play_queue_popover.show_all() @@ -208,10 +213,6 @@ class PlayerControls(Gtk.ActionBar): def on_device_refresh_click(self, button): self.update_device_list(clear=True) - def on_play_queue_row_click(self, listbox, row): - print('play queue click') - print(row) - def create_song_display(self): box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) @@ -392,9 +393,7 @@ class PlayerControls(Gtk.ActionBar): ) self.play_queue_store = Gio.ListStore() - self.play_queue_list = Gtk.ListBox() - self.play_queue_list.connect('row-activated', - self.on_play_queue_row_click) + self.play_queue_list = Gtk.ListBox(activate_on_single_click=False) self.play_queue_list.bind_model( self.play_queue_store, self.create_play_queue_row, @@ -422,7 +421,32 @@ class PlayerControls(Gtk.ActionBar): return vbox def create_play_queue_row(self, model: PlayQueueSong): - row = Gtk.ListBoxRow() + row = Gtk.ListBoxRow( + action_name='app.play-queue-click', + action_target=GLib.Variant('s', model.song_id), + ) + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, margin=3) + + overlay = Gtk.Overlay() + image = SpinnerImage(image_name='play-queue-row-image') + overlay.add(image) + box.add(overlay) + + # Add a play icon overlay if this is the currently playing song. + if model.playing == 'True': + # TODO style this with a a border so that it is visible when the + # cover art looks similar. + play_icon = Gtk.Image( + name='play-queue-playing-icon', + halign=Gtk.Align.CENTER, + valign=Gtk.Align.CENTER, + ) + play_icon.set_from_icon_name( + 'media-playback-start-symbolic', + Gtk.IconSize.DIALOG, + ) + overlay.add_overlay(play_icon) + label = Gtk.Label( label='\n', use_markup=True, @@ -431,16 +455,29 @@ class PlayerControls(Gtk.ActionBar): ellipsize=Pango.EllipsizeMode.END, max_width_chars=35, ) - row.add(label) + box.add(label) + row.add(box) - def update_label(song_details): + def update_image(image_filename): + image.set_from_file(image_filename) + image.set_loading(False) + row.show_all() + + def update_row(song_details): title = util.esc(song_details.title) album = util.esc(song_details.album) artist = util.esc(song_details.artist) label.set_markup(f'{title}\n{util.dot_join(album, artist)}') + row.show_all() + + cover_art_future = CacheManager.get_cover_art_filename( + song_details.coverArt, size=50) + cover_art_future.add_done_callback( + lambda f: GLib.idle_add(update_image, f.result())) song_details_future = CacheManager.get_song_details(model.song_id) song_details_future.add_done_callback( - lambda f: GLib.idle_add(update_label, f.result())) + lambda f: GLib.idle_add(update_row, f.result())) + row.show_all() return row