From b1e537af432b1fc9e74c0712c1844c76864d27ec Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:02:29 +0200 Subject: [PATCH] Library: Initialize only one view on each run Do not create multiple library views and remove the ability to switch between them on the fly. Add an option in settings to select the preferred view. The view will be used the next time Rare is started. --- rare/components/tabs/games/__init__.py | 73 ++---- .../tabs/games/game_widgets/__init__.py | 208 ++++++++++++------ rare/components/tabs/games/head_bar.py | 28 +-- 3 files changed, 159 insertions(+), 150 deletions(-) diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 69ca699d..51fecd11 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -13,8 +13,6 @@ from rare.shared import ( ) from rare.shared import RareCore from rare.models.options import options -from rare.widgets.library_layout import LibraryLayout -from rare.widgets.sliding_stack import SlidingStackedWidget from .game_info import GameInfoTabs from .game_widgets import LibraryWidgetController, LibraryFilter, LibraryOrder, LibraryView from .game_widgets.icon_game_widget import IconGameWidget @@ -54,43 +52,14 @@ class GamesTab(QStackedWidget): self.integrations_page.back_clicked.connect(lambda: self.setCurrentWidget(self.games_page)) self.addWidget(self.integrations_page) - self.view_stack = SlidingStackedWidget(self.games_page) - self.view_stack.setFrameStyle(QFrame.NoFrame) - - self.icon_view_scroll = QScrollArea(self.view_stack) - self.icon_view_scroll.setWidgetResizable(True) - self.icon_view_scroll.setFrameShape(QFrame.StyledPanel) - self.icon_view_scroll.horizontalScrollBar().setDisabled(True) - - self.list_view_scroll = QScrollArea(self.view_stack) - self.list_view_scroll.setWidgetResizable(True) - self.list_view_scroll.setFrameShape(QFrame.StyledPanel) - self.list_view_scroll.horizontalScrollBar().setDisabled(True) - - self.icon_view = QWidget(self.icon_view_scroll) - icon_view_layout = LibraryLayout(self.icon_view) - icon_view_layout.setSpacing(9) - icon_view_layout.setContentsMargins(0, 13, 0, 13) - icon_view_layout.setAlignment(Qt.AlignTop) - - self.list_view = QWidget(self.list_view_scroll) - list_view_layout = QVBoxLayout(self.list_view) - list_view_layout.setContentsMargins(3, 3, 9, 3) - list_view_layout.setAlignment(Qt.AlignTop) - - self.library_controller = LibraryWidgetController(self.icon_view, self.list_view, self) - self.icon_view_scroll.setWidget(self.icon_view) - self.list_view_scroll.setWidget(self.list_view) - self.view_stack.addWidget(self.icon_view_scroll) - self.view_stack.addWidget(self.list_view_scroll) - games_page_layout.addWidget(self.view_stack) + self.view_scroll = QScrollArea(self.games_page) + self.view_scroll.setWidgetResizable(True) + self.view_scroll.setFrameShape(QFrame.StyledPanel) + self.view_scroll.horizontalScrollBar().setDisabled(True) library_view = LibraryView(self.settings.value(*options.library_view)) - self.view_stack.setCurrentWidget(self.list_view_scroll) - if library_view == LibraryView.VLIST: - self.view_stack.setCurrentWidget(self.list_view_scroll) - else: - self.view_stack.setCurrentWidget(self.icon_view_scroll) + self.library_controller = LibraryWidgetController(library_view, self.view_scroll) + games_page_layout.addWidget(self.view_scroll) self.head_bar.search_bar.textChanged.connect(self.search_games) self.head_bar.search_bar.textChanged.connect(self.scroll_to_top) @@ -99,7 +68,6 @@ class GamesTab(QStackedWidget): self.head_bar.orderChanged.connect(self.order_games) self.head_bar.orderChanged.connect(self.scroll_to_top) self.head_bar.refresh_list.clicked.connect(self.library_controller.update_game_views) - self.head_bar.viewChanged.connect(self.change_view) # signals self.signals.game.installed.connect(self.update_count_games_label) @@ -116,11 +84,8 @@ class GamesTab(QStackedWidget): @pyqtSlot() def scroll_to_top(self): - self.icon_view_scroll.verticalScrollBar().setSliderPosition( - self.icon_view_scroll.verticalScrollBar().minimum() - ) - self.list_view_scroll.verticalScrollBar().setSliderPosition( - self.list_view_scroll.verticalScrollBar().minimum() + self.view_scroll.verticalScrollBar().setSliderPosition( + self.view_scroll.verticalScrollBar().minimum() ) @pyqtSlot() @@ -153,24 +118,21 @@ class GamesTab(QStackedWidget): def setup_game_list(self): for rgame in self.rcore.games: - icon_widget, list_widget = self.add_library_widget(rgame) - if not icon_widget or not list_widget: + widget = self.add_library_widget(rgame) + if not widget: logger.warning("Excluding %s from the game list", rgame.app_title) continue - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) self.filter_games(self.head_bar.current_filter()) self.update_count_games_label() def add_library_widget(self, rgame: RareGame): try: - icon_widget, list_widget = self.library_controller.add_game(rgame) + widget = self.library_controller.add_game(rgame) except Exception as e: logger.error("Could not add widget for %s to library: %s", rgame.app_name, e) - return None, None - icon_widget.show_info.connect(self.show_game_info) - list_widget.show_info.connect(self.show_game_info) - return icon_widget, list_widget + return None + widget.show_info.connect(self.show_game_info) + return widget @pyqtSlot(str) def search_games(self, search_text: str = ""): @@ -191,10 +153,3 @@ class GamesTab(QStackedWidget): search_text = t self.library_controller.order_game_views(library_order, search_text.lower()) - - @pyqtSlot(object) - def change_view(self, library_view: LibraryView = LibraryView.COVER): - if library_view == LibraryView.VLIST: - self.view_stack.slideInWidget(self.list_view_scroll) - else: - self.view_stack.slideInWidget(self.icon_view_scroll) diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py index 65f0caa2..369c5140 100644 --- a/rare/components/tabs/games/game_widgets/__init__.py +++ b/rare/components/tabs/games/game_widgets/__init__.py @@ -1,41 +1,33 @@ -from typing import Tuple, List, Union +from abc import abstractmethod +from typing import Tuple, List, Union, Type, TypeVar -from PyQt5.QtCore import QObject, pyqtSlot -from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import QObject, pyqtSlot, Qt +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea from rare.lgndr.core import LegendaryCore from rare.models.game import RareGame from rare.models.signals import GlobalSignals from rare.models.library import LibraryFilter, LibraryOrder, LibraryView from rare.shared import RareCore +from rare.widgets.library_layout import LibraryLayout from .icon_game_widget import IconGameWidget from .list_game_widget import ListGameWidget +ViewWidget = TypeVar("ViewWidget", IconGameWidget, ListGameWidget) -class LibraryWidgetController(QObject): - def __init__(self, icon_container: QWidget, list_container: QWidget, parent: QWidget = None): - super(LibraryWidgetController, self).__init__(parent=parent) - self._icon_container: QWidget = icon_container - self._list_container: QWidget = list_container - self.rcore = RareCore.instance() - self.core: LegendaryCore = self.rcore.core() - self.signals: GlobalSignals = self.rcore.signals() - self.signals.game.installed.connect(self.order_game_views) - self.signals.game.uninstalled.connect(self.order_game_views) +class ViewContainer(QWidget): + def __init__(self, rcore: RareCore, parent=None): + super().__init__(parent=parent) + self.rcore: RareCore = rcore - def add_game(self, rgame: RareGame): - return self.add_widgets(rgame) - - def add_widgets(self, rgame: RareGame) -> Tuple[IconGameWidget, ListGameWidget]: - icon_widget = IconGameWidget(rgame, self._icon_container) - list_widget = ListGameWidget(rgame, self._list_container) - return icon_widget, list_widget + def _add_widget(self, widget_type: Type[ViewWidget], rgame: RareGame) -> ViewWidget: + widget = widget_type(rgame, self) + self.layout().addWidget(widget) + return widget @staticmethod - def __visibility( - widget: Union[IconGameWidget, ListGameWidget], library_filter, search_text - ) -> Tuple[bool, float]: + def __visibility(widget: ViewWidget, library_filter, search_text) -> Tuple[bool, float]: if library_filter == LibraryFilter.HIDDEN: visible = "hidden" in widget.rgame.metadata.tags elif "hidden" in widget.rgame.metadata.tags: @@ -67,85 +59,159 @@ class LibraryWidgetController(QObject): return visible, opacity - def filter_game_views(self, library_filter=LibraryFilter.ALL, search_text: str = ""): - icon_widgets = self._icon_container.findChildren(IconGameWidget) - list_widgets = self._list_container.findChildren(ListGameWidget) - for iw in icon_widgets: - visibility, opacity = self.__visibility(iw, library_filter, search_text) + def _filter_view(self, widget_type: Type[ViewWidget], filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""): + widgets = self.findChildren(widget_type) + for iw in widgets: + visibility, opacity = self.__visibility(iw, filter_by, search_text) iw.setOpacity(opacity) iw.setVisible(visibility) - for lw in list_widgets: - visibility, opacity = self.__visibility(lw, library_filter, search_text) - lw.setOpacity(opacity) - lw.setVisible(visibility) - self.order_game_views(search_text=search_text) - @pyqtSlot() - def order_game_views(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""): - list_widgets = self._list_container.findChildren(ListGameWidget) + def _update_view(self, widget_type: Type[ViewWidget]): + widgets = self.findChildren(widget_type) + app_names = {iw.rgame.app_name for iw in widgets} + games = list(self.rcore.games) + game_app_names = {g.app_name for g in games} + new_app_names = game_app_names.difference(app_names) + for app_name in new_app_names: + game = self.rcore.get_game(app_name) + w = widget_type(game, self) + self.layout().addWidget(w) + + def _find_widget(self, widget_type: Type[ViewWidget], app_name: str) -> ViewWidget: + w = self.findChild(widget_type, app_name) + return w + + @abstractmethod + def order_view(self): + pass + + +class IconViewContainer(ViewContainer): + def __init__(self, rcore: RareCore, parent=None): + super().__init__(rcore, parent=parent) + view_layout = LibraryLayout(self) + view_layout.setSpacing(9) + view_layout.setContentsMargins(0, 13, 0, 13) + view_layout.setAlignment(Qt.AlignTop) + self.setLayout(view_layout) + + def add_widget(self, rgame: RareGame) -> IconGameWidget: + return self._add_widget(IconGameWidget, rgame) + + def filter_view(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""): + self._filter_view(IconGameWidget, filter_by, search_text) + + def update_view(self): + self._update_view(IconGameWidget) + + def find_widget(self, app_name: str) -> ViewWidget: + return self._find_widget(IconGameWidget, app_name) + + def order_view(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""): if search_text: - self._icon_container.layout().sort( + self.layout().sort( lambda x: (search_text not in x.widget().rgame.app_title.lower(),) ) - list_widgets.sort(key=lambda x: (search_text not in x.rgame.app_title.lower(),)) else: if (newest := order_by == LibraryOrder.NEWEST) or order_by == LibraryOrder.OLDEST: # Sort by grant date - self._icon_container.layout().sort( + self.layout().sort( key=lambda x: (x.widget().rgame.is_installed, not x.widget().rgame.is_non_asset, x.widget().rgame.grant_date()), reverse=newest, ) + elif order_by == LibraryOrder.RECENT: + # Sort by recently played + self.layout().sort( + key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.metadata.last_played), + reverse=True, + ) + else: + # Sort by title + self.layout().sort( + key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.app_title) + ) + + +class ListViewContainer(ViewContainer): + def __init__(self, rcore, parent=None): + super().__init__(rcore, parent=parent) + view_layout = QVBoxLayout(self) + view_layout.setContentsMargins(3, 3, 9, 3) + view_layout.setAlignment(Qt.AlignTop) + self.setLayout(view_layout) + + def add_widget(self, rgame: RareGame) -> ListGameWidget: + return self._add_widget(ListGameWidget, rgame) + + def filter_view(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""): + self._filter_view(ListGameWidget, filter_by, search_text) + + def update_view(self): + self._update_view(ListGameWidget) + + def find_widget(self, app_name: str) -> ViewWidget: + return self._find_widget(ListGameWidget, app_name) + + def order_view(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""): + list_widgets = self.findChildren(ListGameWidget) + if search_text: + list_widgets.sort(key=lambda x: (search_text not in x.rgame.app_title.lower(),)) + else: + if (newest := order_by == LibraryOrder.NEWEST) or order_by == LibraryOrder.OLDEST: list_widgets.sort( key=lambda x: (x.rgame.is_installed, not x.rgame.is_non_asset, x.rgame.grant_date()), reverse=newest, ) elif order_by == LibraryOrder.RECENT: - # Sort by recently played - self._icon_container.layout().sort( - key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.metadata.last_played), - reverse=True, - ) list_widgets.sort( key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.metadata.last_played), reverse=True, ) else: - # Sort by title - self._icon_container.layout().sort( - key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.app_title) - ) list_widgets.sort( key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.app_title) ) - for idx, wl in enumerate(list_widgets): - self._list_container.layout().insertWidget(idx, wl) + self.layout().insertWidget(idx, wl) + + +class LibraryWidgetController(QObject): + def __init__(self, view: LibraryView, parent: QScrollArea = None): + super(LibraryWidgetController, self).__init__(parent=parent) + self.rcore = RareCore.instance() + self.core: LegendaryCore = self.rcore.core() + self.signals: GlobalSignals = self.rcore.signals() + + if view == LibraryView.COVER: + self._container: IconViewContainer = IconViewContainer(self.rcore, parent) + else: + self._container: ListViewContainer = ListViewContainer(self.rcore, parent) + parent.setWidget(self._container) + + self.signals.game.installed.connect(self.order_game_views) + self.signals.game.uninstalled.connect(self.order_game_views) + + def add_game(self, rgame: RareGame): + return self.add_widgets(rgame) + + def add_widgets(self, rgame: RareGame) -> ViewWidget: + return self._container.add_widget(rgame) + + def filter_game_views(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""): + self._container.filter_view(filter_by, search_text) + self.order_game_views(search_text=search_text) + + @pyqtSlot() + def order_game_views(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""): + self._container.order_view(order_by, search_text) @pyqtSlot() @pyqtSlot(list) def update_game_views(self, app_names: List[str] = None): if app_names: return - # lk: base it on icon widgets, the two lists should be identical - icon_widgets = self._icon_container.findChildren(IconGameWidget) - list_widgets = self._list_container.findChildren(ListGameWidget) - icon_app_names = {iw.rgame.app_name for iw in icon_widgets} - list_app_names = {lw.rgame.app_name for lw in list_widgets} - games = list(self.rcore.games) - game_app_names = {g.app_name for g in games} - new_icon_app_names = game_app_names.difference(icon_app_names) - new_list_app_names = game_app_names.difference(list_app_names) - for app_name in new_icon_app_names: - game = self.rcore.get_game(app_name) - iw = IconGameWidget(game) - self._icon_container.layout().addWidget(iw) - for app_name in new_list_app_names: - game = self.rcore.get_game(app_name) - lw = ListGameWidget(game) - self._list_container.layout().addWidget(lw) + self._container.update_view() self.order_game_views() - def __find_widget(self, app_name: str) -> Tuple[Union[IconGameWidget, None], Union[ListGameWidget, None]]: - iw = self._icon_container.findChild(IconGameWidget, app_name) - lw = self._list_container.findChild(ListGameWidget, app_name) - return iw, lw + def __find_widget(self, app_name: str) -> Union[ViewWidget, None]: + return self._container.find_widget(app_name) diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index 4a122e56..b80527cb 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -11,8 +11,8 @@ from PyQt5.QtWidgets import ( ) from rare.shared import RareCore -from rare.models.options import options, LibraryFilter, LibraryOrder, LibraryView -from rare.utils.extra_widgets import SelectViewWidget, ButtonLineEdit +from rare.models.options import options, LibraryFilter, LibraryOrder +from rare.utils.extra_widgets import ButtonLineEdit from rare.utils.misc import icon @@ -107,7 +107,7 @@ class GameListHeadBar(QWidget): self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Game")) self.search_bar.setObjectName("SearchBar") self.search_bar.setFrame(False) - self.search_bar.setMinimumWidth(200) + self.search_bar.setMinimumWidth(250) installed_tooltip = self.tr("Installed games") self.installed_icon = QLabel(parent=self) @@ -125,10 +125,6 @@ class GameListHeadBar(QWidget): self.available_label = QLabel(parent=self) self.available_label.setToolTip(available_tooltip) - view = LibraryView(QSettings(self).value(*options.library_view)) - self.library_view = SelectViewWidget(view == LibraryView.COVER) - self.library_view.toggled.connect(self.__view_changed) - self.refresh_list = QPushButton(parent=self) self.refresh_list.setIcon(icon("fa.refresh")) # Reload icon self.refresh_list.clicked.connect(self.__refresh_clicked) @@ -139,16 +135,14 @@ class GameListHeadBar(QWidget): layout.addWidget(self.order) layout.addStretch(0) layout.addWidget(integrations) - layout.addStretch(2) + layout.addStretch(3) layout.addWidget(self.search_bar) - layout.addStretch(2) + layout.addStretch(4) layout.addWidget(self.installed_icon) layout.addWidget(self.installed_label) layout.addWidget(self.available_icon) layout.addWidget(self.available_label) - layout.addStretch(2) - layout.addWidget(self.library_view) - layout.addStretch(2) + layout.addStretch(4) layout.addWidget(self.refresh_list) def set_games_count(self, inst: int, avail: int) -> None: @@ -160,7 +154,7 @@ class GameListHeadBar(QWidget): self.rcore.fetch() def current_filter(self) -> LibraryFilter: - return LibraryFilter(self.filter.currentData(Qt.UserRole)) + return self.filter.currentData(Qt.UserRole) @pyqtSlot(int) def __filter_changed(self, index: int): @@ -169,16 +163,10 @@ class GameListHeadBar(QWidget): self.settings.setValue(options.library_filter.key, int(data)) def current_order(self) -> LibraryOrder: - return LibraryOrder(self.order.currentData(Qt.UserRole)) + return self.order.currentData(Qt.UserRole) @pyqtSlot(int) def __order_changed(self, index: int): data = self.order.itemData(index, Qt.UserRole) self.orderChanged.emit(data) self.settings.setValue(options.library_order.key, int(data)) - - @pyqtSlot(bool) - def __view_changed(self, icon_view: bool): - view = LibraryView.COVER if icon_view else LibraryView.VLIST - self.viewChanged.emit(view) - self.settings.setValue(options.library_view.key, int(view))