[gnome-music/wip/mschraal/albumview-covers-on-demand: 12/14] albumsview: Load covers on demand



commit a6ba6068b1949f09455f78b84245e5b379f2aff6
Author: Marinus Schraal <mschraal gnome org>
Date:   Fri Oct 18 17:03:01 2019 +0200

    albumsview: Load covers on demand
    
    Loading all covers on startup is a very demanding task, especially on a
    fresh install. Previously Music was using a delayed loading hack, but it
    was still resource intensive and suboptimal.
    
    Instead load covers on demand. GTK does not currently provide a proper
    way of doing this, so the implementation is hackish.

 gnomemusic/views/albumsview.py   | 57 +++++++++++++++++++++++++++++++++++++++-
 gnomemusic/views/searchview.py   |  1 +
 gnomemusic/widgets/albumcover.py | 23 +++++++++-------
 3 files changed, 71 insertions(+), 10 deletions(-)
---
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py
index c0f70a91..9d32b197 100644
--- a/gnomemusic/views/albumsview.py
+++ b/gnomemusic/views/albumsview.py
@@ -22,8 +22,10 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+import math
+
 from gettext import gettext as _
-from gi.repository import GObject, Gtk
+from gi.repository import GLib, GObject, Gtk
 
 from gnomemusic.widgets.headerbar import HeaderBar
 from gnomemusic.widgets.albumcover import AlbumCover
@@ -62,6 +64,8 @@ class AlbumsView(Gtk.Stack):
 
         self._window = application.props.window
         self._headerbar = self._window._headerbar
+        self._timeout_id = None
+        self._viewport = self._all_albums.get_child()
 
         model = self._window._app.props.coremodel.props.albums_sort
         self._flowbox.bind_model(model, self._create_widget)
@@ -83,8 +87,59 @@ class AlbumsView(Gtk.Stack):
         self.connect(
             "notify::search-mode-active", self._on_search_mode_changed)
 
+        self._all_albums.props.vadjustment.connect(
+            "value-changed", self._on_vadjustment_changed)
+        self._all_albums.props.vadjustment.connect(
+            "changed", self._on_vadjustment_changed)
+
         self.show_all()
 
+    def _on_vadjustment_changed(self, adjustment):
+        if self._timeout_id is not None:
+            GLib.source_remove(self._timeout_id)
+            self._timeout_id = None
+
+        self._timeout_id = GLib.timeout_add(
+            200, self._retrieve_covers, adjustment.props.value,
+            priority=GLib.PRIORITY_LOW)
+
+    def _retrieve_covers(self, old_adjustment):
+        adjustment = self._all_albums.props.vadjustment.props.value
+
+        if old_adjustment != adjustment:
+            return GLib.SOURCE_CONTINUE
+
+        # This is blatant hack to get the top left AlbumCover. It polls
+        # the flowbox twice if needed to handle possible margins
+        # between AlbumCovers.
+        top_left_cover = self._flowbox.get_child_at_pos(100, adjustment + 100)
+        if not top_left_cover:
+            top_left_cover = self._flowbox.get_child_at_pos(
+                50, adjustment + 50)
+        if not top_left_cover:
+            return GLib.SOURCE_CONTINUE
+
+        cover_size, _ = top_left_cover.get_allocated_size()
+        viewport_size, _ = self._viewport.get_allocated_size()
+
+        covers_row = math.ceil(viewport_size.width / cover_size.width)
+        covers_col = math.ceil(viewport_size.height / cover_size.height)
+
+        children = self._flowbox.get_children()
+        retrieve_list = []
+        for i, albumcover in enumerate(children):
+            if top_left_cover == albumcover:
+                retrieve_covers = covers_row * covers_col
+                retrieve_list = children[i:i + retrieve_covers]
+                break
+
+        for albumcover in retrieve_list:
+            albumcover.retrieve()
+
+        self._timeout_id = None
+
+        return GLib.SOURCE_REMOVE
+
     def _on_selection_mode_changed(self, widget, data=None):
         if not self.props.selection_mode:
             self.unselect_all()
diff --git a/gnomemusic/views/searchview.py b/gnomemusic/views/searchview.py
index 0cc31c56..40ed115b 100644
--- a/gnomemusic/views/searchview.py
+++ b/gnomemusic/views/searchview.py
@@ -160,6 +160,7 @@ class SearchView(Gtk.Stack):
 
     def _create_album_widget(self, corealbum):
         album_widget = AlbumCover(corealbum)
+        album_widget.retrieve()
 
         self.bind_property(
             "selection-mode", album_widget, "selection-mode",
diff --git a/gnomemusic/widgets/albumcover.py b/gnomemusic/widgets/albumcover.py
index ae6b82f8..7b9cfaa7 100644
--- a/gnomemusic/widgets/albumcover.py
+++ b/gnomemusic/widgets/albumcover.py
@@ -24,7 +24,7 @@
 
 import gi
 gi.require_version('Grl', '0.3')
-from gi.repository import Gdk, GLib, GObject, Gtk
+from gi.repository import Gdk, GObject, Gtk
 
 from gnomemusic import log
 from gnomemusic.albumartcache import Art
@@ -68,6 +68,7 @@ class AlbumCover(Gtk.FlowBoxChild):
         AlbumCover._nr_albums += 1
 
         self._corealbum = corealbum
+        self._retrieved = False
 
         self._tooltip = TwoLineTip()
 
@@ -96,16 +97,20 @@ class AlbumCover(Gtk.FlowBoxChild):
 
         self.show()
 
-        # FIXME: To work around slow updating of the albumsview,
-        # load album covers with a fixed delay. This results in a
-        # quick first show with a placeholder cover and then a
-        # reasonably responsive view while loading the actual
-        # covers.
         self._update_cover_id = self._cover_stack.connect(
             "updated", self._on_cover_stack_updated)
-        GLib.timeout_add(
-            50 * self._nr_albums, self._cover_stack.update, self._corealbum,
-            priority=GLib.PRIORITY_LOW)
+
+    def retrieve(self):
+        """Start retrieving the actual album cover
+
+        Cover retrieval is an expensive operation, so use this call to
+        start the retrieval process when needed.
+        """
+        if self._retrieved:
+            return
+
+        self._retrieved = True
+        self._cover_stack.update(self._corealbum)
 
     @GObject.Property(type=CoreAlbum, flags=GObject.ParamFlags.READABLE)
     def corealbum(self):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]