Correct concurrency with GLib; loading indictors
This commit is contained in:
@@ -5,7 +5,7 @@ import mpv
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, GLib, Gdk
|
||||
from gi.repository import Gio, Gtk, GLib, Gdk, GObject
|
||||
|
||||
from .ui.main import MainWindow
|
||||
from .ui.configure_servers import ConfigureServersDialog
|
||||
@@ -37,8 +37,11 @@ class LibremsonicApp(Gtk.Application):
|
||||
|
||||
@self.player.property_observer('time-pos')
|
||||
def time_observer(_name, value):
|
||||
self.window.player_controls.update_scrubber(
|
||||
value, self.state.current_song.duration)
|
||||
GLib.idle_add(
|
||||
self.window.player_controls.update_scrubber,
|
||||
value,
|
||||
self.state.current_song.duration,
|
||||
)
|
||||
|
||||
@self.player.property_observer('eof-reached')
|
||||
def file_end(_, value):
|
||||
@@ -201,19 +204,21 @@ class LibremsonicApp(Gtk.Application):
|
||||
dialog.destroy()
|
||||
|
||||
def update_window(self):
|
||||
self.window.update(self.state)
|
||||
GLib.idle_add(self.window.update, self.state)
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_song_details(*a, **k),
|
||||
)
|
||||
def play_song(self, song: Child):
|
||||
self.state.playing = True
|
||||
self.state.current_song = song
|
||||
future = CacheManager.get_song_filename(song)
|
||||
self.update_window()
|
||||
future.add_done_callback(lambda f: self.play_file(f.result()))
|
||||
|
||||
def play_file(self, song_file):
|
||||
self.player.loadfile(song_file, 'replace')
|
||||
self.player.pause = False
|
||||
self.update_window()
|
||||
song_filename_future = CacheManager.get_song_filename(song)
|
||||
|
||||
def filename_future_done(song_file):
|
||||
self.state.current_song = song
|
||||
self.update_window()
|
||||
self.player.loadfile(song_file, 'replace')
|
||||
self.player.pause = False
|
||||
|
||||
song_filename_future.add_done_callback(
|
||||
lambda f: GLib.idle_add(filename_future_done, f.result()), )
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import math
|
||||
from time import sleep
|
||||
from deprecated import deprecated
|
||||
from typing import Any, Optional, Dict, List, Union, Iterator, cast
|
||||
from datetime import datetime
|
||||
|
@@ -1,11 +1,26 @@
|
||||
/* ********** Playback Controls ********** */
|
||||
#album-artwork {
|
||||
/* ********** Playlist ********** */
|
||||
#playlist-list-listbox row {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#playlist-list-spinner:checked {
|
||||
margin: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#playlist-artwork-spinner {
|
||||
min-height: 35px;
|
||||
min-width: 35px;
|
||||
}
|
||||
|
||||
#playlist-album-artwork {
|
||||
min-height: 200px;
|
||||
min-width: 200px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#album-artwork.collapsed {
|
||||
#playlist-album-artwork.collapsed {
|
||||
min-height: 40px;
|
||||
min-width: 40px;
|
||||
}
|
||||
@@ -27,6 +42,11 @@
|
||||
}
|
||||
|
||||
/* ********** Playback Controls ********** */
|
||||
#player-controls-album-artwork #player-controls-album-artwork {
|
||||
min-height: 70px;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
#player-controls-bar #play-button {
|
||||
min-height: 45px;
|
||||
min-width: 35px;
|
||||
|
@@ -49,11 +49,9 @@ class PlayerControls(Gtk.ActionBar):
|
||||
has_next_song = current < len(state.play_queue) - 1
|
||||
|
||||
self.song_scrubber.set_sensitive(has_current_song)
|
||||
self.repeat_button.set_sensitive(has_current_song)
|
||||
self.prev_button.set_sensitive(has_current_song and has_prev_song)
|
||||
self.play_button.set_sensitive(has_current_song)
|
||||
self.next_button.set_sensitive(has_current_song and has_next_song)
|
||||
self.shuffle_button.set_sensitive(has_current_song)
|
||||
|
||||
if not has_current_song:
|
||||
return
|
||||
@@ -92,7 +90,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
def create_song_display(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.album_art = Gtk.Image()
|
||||
self.album_art = Gtk.Image(name="player-controls-album-artwork")
|
||||
box.pack_start(self.album_art, False, False, 5)
|
||||
|
||||
details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, Pango, GObject
|
||||
from gi.repository import Gio, Gtk, Pango, GObject, GLib
|
||||
|
||||
from libremsonic.server.api_objects import Child, PlaylistWithSongs
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
@@ -52,7 +52,12 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
playlist_list_vbox.add(playlist_list_actions)
|
||||
|
||||
list_scroll_window = Gtk.ScrolledWindow(min_content_width=220)
|
||||
self.playlist_list = Gtk.ListBox()
|
||||
self.playlist_list = Gtk.ListBox(name='playlist-list-listbox')
|
||||
|
||||
self.playlist_list_loading = Gtk.Spinner(name='playlist-list-spinner',
|
||||
active=True)
|
||||
self.playlist_list.add(self.playlist_list_loading)
|
||||
|
||||
self.playlist_list.connect('row-activated', self.on_playlist_selected)
|
||||
list_scroll_window.add(self.playlist_list)
|
||||
playlist_list_vbox.pack_start(list_scroll_window, True, True, 0)
|
||||
@@ -63,8 +68,8 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
|
||||
# The playlist view on the right side
|
||||
# =====================================================================
|
||||
loading_overlay = Gtk.Overlay(name='playlist-view-overlay')
|
||||
playlist_view_scroll_window = Gtk.ScrolledWindow()
|
||||
playlist_view_scroll_window.do_scroll_child = lambda: None
|
||||
playlist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
# Playlist info panel
|
||||
@@ -73,8 +78,17 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
name='playlist-info-panel',
|
||||
orientation=Gtk.Orientation.HORIZONTAL,
|
||||
)
|
||||
self.playlist_artwork = Gtk.Image(name='album-artwork')
|
||||
self.big_info_panel.pack_start(self.playlist_artwork, False, False, 0)
|
||||
|
||||
artwork_overlay = Gtk.Overlay()
|
||||
self.playlist_artwork = Gtk.Image(name='playlist-album-artwork')
|
||||
artwork_overlay.add(self.playlist_artwork)
|
||||
|
||||
self.artwork_spinner = Gtk.Spinner(name='playlist-artwork-spinner',
|
||||
active=True,
|
||||
halign=Gtk.Align.CENTER,
|
||||
valign=Gtk.Align.CENTER)
|
||||
artwork_overlay.add_overlay(self.artwork_spinner)
|
||||
self.big_info_panel.pack_start(artwork_overlay, False, False, 0)
|
||||
|
||||
# Name, comment, number of songs, etc.
|
||||
playlist_details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
@@ -141,13 +155,27 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
playlist_box.pack_end(self.playlist_songs, True, True, 0)
|
||||
|
||||
playlist_view_scroll_window.add(playlist_box)
|
||||
loading_overlay.add(playlist_view_scroll_window)
|
||||
|
||||
self.pack2(playlist_view_scroll_window, True, False)
|
||||
playlist_view_spinner = Gtk.Spinner(active=True)
|
||||
playlist_view_spinner.start()
|
||||
|
||||
self.playlist_view_loading_box = Gtk.Alignment(
|
||||
name='playlist-view-overlay',
|
||||
xalign=0.5,
|
||||
yalign=0.5,
|
||||
xscale=0.1,
|
||||
yscale=0.1)
|
||||
self.playlist_view_loading_box.add(playlist_view_spinner)
|
||||
loading_overlay.add_overlay(self.playlist_view_loading_box)
|
||||
|
||||
self.pack2(loading_overlay, True, False)
|
||||
|
||||
# Event Handlers
|
||||
# =========================================================================
|
||||
def on_playlist_selected(self, playlist_list, row):
|
||||
playlist_id = self.playlist_ids[row.get_index()]
|
||||
# Use row index - 1 due to the loading indicator.
|
||||
playlist_id = self.playlist_ids[row.get_index() - 1]
|
||||
self.update_playlist_view(playlist_id)
|
||||
|
||||
def on_list_refresh_click(self, button):
|
||||
@@ -168,8 +196,32 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
|
||||
def update(self, state: ApplicationState):
|
||||
self.update_playlist_list()
|
||||
self.set_playlist_view_loading(False)
|
||||
self.set_playlist_art_loading(False)
|
||||
|
||||
@util.async_callback(lambda *a, **k: CacheManager.get_playlists(*a, **k))
|
||||
def set_playlist_list_loading(self, loading_status):
|
||||
if loading_status:
|
||||
self.playlist_list_loading.show()
|
||||
else:
|
||||
self.playlist_list_loading.hide()
|
||||
|
||||
def set_playlist_view_loading(self, loading_status):
|
||||
if loading_status:
|
||||
self.playlist_view_loading_box.show()
|
||||
self.artwork_spinner.show()
|
||||
else:
|
||||
self.playlist_view_loading_box.hide()
|
||||
|
||||
def set_playlist_art_loading(self, loading_status):
|
||||
if loading_status:
|
||||
self.artwork_spinner.show()
|
||||
else:
|
||||
self.artwork_spinner.hide()
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_playlists(*a, **k),
|
||||
before_fn=lambda self: self.set_playlist_list_loading(True),
|
||||
)
|
||||
def update_playlist_list(self, playlists: List[PlaylistWithSongs]):
|
||||
not_seen = set(self.playlist_ids)
|
||||
|
||||
@@ -184,8 +236,12 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
print(playlist_id)
|
||||
|
||||
self.playlist_list.show_all()
|
||||
self.set_playlist_list_loading(False)
|
||||
|
||||
@util.async_callback(lambda *a, **k: CacheManager.get_playlist(*a, **k))
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_playlist(*a, **k),
|
||||
before_fn=lambda self: self.set_playlist_view_loading(True),
|
||||
)
|
||||
def update_playlist_view(self, playlist):
|
||||
# Update the Playlist Info panel
|
||||
self.update_playlist_artwork(playlist.coverArt)
|
||||
@@ -215,17 +271,20 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
util.format_song_duration(song.duration),
|
||||
song.id,
|
||||
])
|
||||
self.set_playlist_view_loading(False)
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_cover_art_filename(*a, **k),
|
||||
before_fn=lambda self: self.set_playlist_art_loading(True),
|
||||
)
|
||||
def update_playlist_artwork(self, cover_art_filename):
|
||||
self.playlist_artwork.set_from_file(cover_art_filename)
|
||||
self.set_playlist_art_loading(False)
|
||||
|
||||
def create_playlist_label(self, playlist: PlaylistWithSongs):
|
||||
return self.make_label(f'<b>{playlist.name}</b>',
|
||||
use_markup=True,
|
||||
margin=10)
|
||||
margin=12)
|
||||
|
||||
def pluralize(self, string, number, pluralized_form=None):
|
||||
if number != 1:
|
||||
|
@@ -4,7 +4,7 @@ import functools
|
||||
from concurrent.futures import Future
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk
|
||||
from gi.repository import Gio, Gtk, GObject, GLib
|
||||
|
||||
|
||||
def button_with_icon(
|
||||
@@ -27,7 +27,7 @@ def format_song_duration(duration_secs) -> str:
|
||||
return f'{duration_secs // 60}:{duration_secs % 60:02}'
|
||||
|
||||
|
||||
def async_callback(future_fn):
|
||||
def async_callback(future_fn, before_fn=None):
|
||||
"""
|
||||
Defines the ``async_callback`` decorator.
|
||||
|
||||
@@ -43,8 +43,12 @@ def async_callback(future_fn):
|
||||
def decorator(callback_fn):
|
||||
@functools.wraps(callback_fn)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if before_fn:
|
||||
before_fn(self)
|
||||
|
||||
future: Future = future_fn(*args, **kwargs)
|
||||
future.add_done_callback(lambda f: callback_fn(self, f.result()))
|
||||
future.add_done_callback(
|
||||
lambda f: GLib.idle_add(callback_fn, self, f.result()), )
|
||||
|
||||
return wrapper
|
||||
|
||||
|
Reference in New Issue
Block a user