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))