mirror of
https://github.com/lutris/lutris
synced 2024-09-05 23:46:21 +00:00
Fix updates of the list view when media are customized or reset
What an ugly mess! queue_draw() on every window does not work on the ListView, though I don't understand why not. This replaces that with a notification system so that the views can register callbacks to redraw themselves. I've added a Notification class to tame this beast, in the hope that this will at least be reusable.
This commit is contained in:
parent
8d665cd92a
commit
81969e5d02
|
@ -17,7 +17,7 @@ from lutris.gui.dialogs.delegates import DialogInstallUIDelegate
|
|||
from lutris.gui.widgets.common import Label, NumberEntry, SlugEntry
|
||||
from lutris.gui.widgets.notifications import send_notification
|
||||
from lutris.gui.widgets.scaled_image import ScaledImage
|
||||
from lutris.gui.widgets.utils import get_image_file_format, invalidate_media_caches
|
||||
from lutris.gui.widgets.utils import MEDIA_CACHE_INVALIDATED, get_image_file_format
|
||||
from lutris.runners import import_runner
|
||||
from lutris.services.lutris import LutrisBanner, LutrisCoverart, LutrisIcon, download_lutris_media
|
||||
from lutris.util.jobs import AsyncCall
|
||||
|
@ -725,7 +725,7 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
|
|||
# JPEG encoding looks rather better at high quality;
|
||||
# PNG encoding just ignores this option.
|
||||
pixbuf.savev(dest_path, file_format, ["quality"], ["100"])
|
||||
invalidate_media_caches()
|
||||
MEDIA_CACHE_INVALIDATED.fire()
|
||||
return image_type
|
||||
|
||||
def refresh_image(self, image_type):
|
||||
|
@ -737,7 +737,7 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
|
|||
if os.path.isfile(dest_path):
|
||||
os.remove(dest_path)
|
||||
download_lutris_media(self.game.slug)
|
||||
invalidate_media_caches()
|
||||
MEDIA_CACHE_INVALIDATED.fire()
|
||||
return image_type
|
||||
|
||||
def image_refreshed_cb(self, image_type, _error):
|
||||
|
|
|
@ -770,11 +770,15 @@ class LutrisWindow(Gtk.ApplicationWindow,
|
|||
regenerates it. This is used to update view settings that can only be
|
||||
set during view construction, and not updated later."""
|
||||
if view_type in self.views:
|
||||
view = self.views[view_type]
|
||||
scrolledwindow = self.games_stack.get_child_by_name(view_type)
|
||||
scrolledwindow.remove(self.views[view_type])
|
||||
del self.views["grid"]
|
||||
scrolledwindow.remove(view)
|
||||
del self.views[view_type]
|
||||
if self.current_view_type == view_type:
|
||||
self.redraw_view()
|
||||
# Because the view has hooks and such hooked up, it must be explicitly
|
||||
# destroyed to disconnect everything.
|
||||
view.destroy()
|
||||
|
||||
def update_view_settings(self):
|
||||
if self.current_view and self.current_view_type == "grid":
|
||||
|
|
|
@ -8,6 +8,7 @@ from lutris.database.services import ServiceGameCollection
|
|||
from lutris.game import Game
|
||||
from lutris.game_actions import BaseGameActions, get_game_actions
|
||||
from lutris.gui.widgets.contextual_menu import ContextualMenu
|
||||
from lutris.gui.widgets.utils import MEDIA_CACHE_INVALIDATED
|
||||
from lutris.util.log import logger
|
||||
|
||||
|
||||
|
@ -20,18 +21,27 @@ class GameView:
|
|||
|
||||
def __init__(self, service):
|
||||
self.service = service
|
||||
self.cache_notification_id = None
|
||||
self.game_start_hook_id = None
|
||||
self.image_renderer = None
|
||||
|
||||
def connect_signals(self):
|
||||
"""Signal handlers common to all views"""
|
||||
self.cache_notification_id = MEDIA_CACHE_INVALIDATED.register(self.on_media_cache_invalidated)
|
||||
|
||||
self.connect("destroy", self.on_destroy)
|
||||
self.connect("button-press-event", self.popup_contextual_menu)
|
||||
self.connect("key-press-event", self.handle_key_press)
|
||||
|
||||
self.game_start_hook_id = GObject.add_emission_hook(Game, "game-start", self.on_game_start)
|
||||
|
||||
def on_media_cache_invalidated(self):
|
||||
self.queue_draw()
|
||||
|
||||
def on_destroy(self, _widget):
|
||||
if self.cache_notification_id:
|
||||
MEDIA_CACHE_INVALIDATED.unregister(self.cache_notification_id)
|
||||
|
||||
if self.game_start_hook_id:
|
||||
GObject.remove_emission_hook(Game, "game-start", self.game_start_hook_id)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Loads game media in parallel"""
|
||||
import concurrent.futures
|
||||
|
||||
from lutris.gui.widgets.utils import invalidate_media_caches
|
||||
from lutris.gui.widgets.utils import MEDIA_CACHE_INVALIDATED
|
||||
from lutris.util import system
|
||||
from lutris.util.log import logger
|
||||
|
||||
|
@ -30,5 +30,5 @@ def download_media(media_urls, service_media):
|
|||
icons[slug] = path
|
||||
|
||||
if icons:
|
||||
invalidate_media_caches()
|
||||
MEDIA_CACHE_INVALIDATED.fire()
|
||||
return icons
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
from typing import Callable
|
||||
|
||||
from lutris.util.jobs import schedule_at_idle
|
||||
|
||||
|
||||
class Notification:
|
||||
"""A class to inform interested code of changes in a global, like a signal but not attached to any
|
||||
object."""
|
||||
|
||||
def __init__(self):
|
||||
self._generation_number = 0
|
||||
self._callbacks = {}
|
||||
self._next_callback_id = 1
|
||||
self._scheduled_callbacks = set()
|
||||
|
||||
def fire(self) -> None:
|
||||
"""Signals that the thing, whatever it is, has happened. This increments the generation number,
|
||||
and schedules the callbacks to run (if they are not scheduled already)."""
|
||||
self._generation_number += 1
|
||||
self._scheduled_callbacks.update(self._callbacks.values())
|
||||
schedule_at_idle(self._notify)
|
||||
|
||||
@property
|
||||
def generation_number(self) -> int:
|
||||
"""Returns a number that is incremented on each call to fire(). This can be polled
|
||||
passively, when registering a callback is inappropriate."""
|
||||
return self._generation_number
|
||||
|
||||
def register(self, callback: Callable[[], None]) -> int:
|
||||
"""Registers a callback to be called after the thing, whatever it is, has happened;
|
||||
fire() schedules callbacks to be called at idle time on the main thread.
|
||||
|
||||
Note that a callback will be kept alive until unregistered, and this can keep
|
||||
large objects alive until then.
|
||||
|
||||
Returns an id number to use to unregister the callback."""
|
||||
callback_id = self._next_callback_id
|
||||
self._callbacks[callback_id] = callback
|
||||
self._next_callback_id += 1
|
||||
return callback_id
|
||||
|
||||
def unregister(self, callback_id: int) -> None:
|
||||
"""Unregisters a callback that register() had registered."""
|
||||
self._callbacks.pop(callback_id, None)
|
||||
|
||||
def _notify(self):
|
||||
while self._scheduled_callbacks:
|
||||
callback = self._scheduled_callbacks.pop()
|
||||
callback()
|
|
@ -11,8 +11,7 @@ import cairo
|
|||
from gi.repository import GLib, GObject, Gtk, Pango, PangoCairo
|
||||
|
||||
from lutris.gui.widgets.utils import (
|
||||
get_default_icon_path, get_media_generation_number, get_runtime_icon_path, get_scaled_surface_by_path,
|
||||
get_surface_size
|
||||
MEDIA_CACHE_INVALIDATED, get_default_icon_path, get_runtime_icon_path, get_scaled_surface_by_path, get_surface_size
|
||||
)
|
||||
from lutris.scanners.lutris import is_game_missing
|
||||
|
||||
|
@ -472,8 +471,8 @@ class GridViewCellRendererImage(Gtk.CellRenderer):
|
|||
in this render, but we'll clear that cache when the media generation number is changed,
|
||||
or certain properties are. We also age surfaces from the cache at idle time after
|
||||
rendering."""
|
||||
if self.cached_surface_generation != get_media_generation_number():
|
||||
self.cached_surface_generation = get_media_generation_number()
|
||||
if self.cached_surface_generation != MEDIA_CACHE_INVALIDATED.generation_number:
|
||||
self.cached_surface_generation = MEDIA_CACHE_INVALIDATED.generation_number
|
||||
self.clear_cache()
|
||||
|
||||
key = widget, path, size, preserve_aspect_ratio
|
||||
|
|
|
@ -6,8 +6,8 @@ import cairo
|
|||
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk
|
||||
|
||||
from lutris import settings
|
||||
from lutris.gui.widgets import Notification
|
||||
from lutris.util import datapath, magic, system
|
||||
from lutris.util.jobs import schedule_at_idle
|
||||
from lutris.util.log import logger
|
||||
|
||||
try:
|
||||
|
@ -17,8 +17,7 @@ except ImportError:
|
|||
|
||||
ICON_SIZE = (32, 32)
|
||||
BANNER_SIZE = (184, 69)
|
||||
|
||||
_surface_generation_number = 0
|
||||
MEDIA_CACHE_INVALIDATED = Notification()
|
||||
|
||||
|
||||
def get_main_window(widget):
|
||||
|
@ -103,29 +102,6 @@ def get_scaled_surface_by_path(path, size, device_scale, preserve_aspect_ratio=T
|
|||
return surface
|
||||
|
||||
|
||||
def get_media_generation_number():
|
||||
"""Returns a number that is incremented whenever cached media may no longer
|
||||
be valid. Caller can check to see if this has changed before using their own caches."""
|
||||
return _surface_generation_number
|
||||
|
||||
|
||||
def invalidate_media_caches():
|
||||
"""Increments the media generation number; this indicates that cached media
|
||||
from earlier generations may be invalid and should be reloaded."""
|
||||
global _surface_generation_number
|
||||
_surface_generation_number += 1
|
||||
schedule_at_idle(queue_draw_all_windows)
|
||||
|
||||
|
||||
def queue_draw_all_windows():
|
||||
"""Scans through all windows and queues a redraw for each one;
|
||||
we trigger this when invalidate_media_caches() has been called."""
|
||||
application = Gio.Application.get_default()
|
||||
if application:
|
||||
for window in application.get_windows():
|
||||
window.queue_draw()
|
||||
|
||||
|
||||
def get_default_icon_path(size):
|
||||
"""Returns the path to the default icon for the size given; it's
|
||||
a Lutris icon for a square size, and a gradient for other sizes."""
|
||||
|
|
Loading…
Reference in a new issue