diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py index 03c245a9..c01448cf 100644 --- a/rare/components/tabs/__init__.py +++ b/rare/components/tabs/__init__.py @@ -25,18 +25,18 @@ class TabWidget(QTabWidget): self.setTabBar(MainTabBar(disabled_tab)) # Generate Tabs - self.games_tab = GamesTab() + self.games_tab = GamesTab(self) self.addTab(self.games_tab, self.tr("Games")) if not self.args.offline: # updates = self.games_tab.default_widget.game_list.updates - self.downloads_tab = DownloadsTab(self.games_tab.updates) + self.downloads_tab = DownloadsTab(self.games_tab.game_updates, self) self.addTab( self.downloads_tab, "Downloads" + ( - " (" + str(len(self.games_tab.updates)) + ")" - if len(self.games_tab.updates) != 0 + " (" + str(len(self.games_tab.game_updates)) + ")" + if len(self.games_tab.game_updates) != 0 else "" ), ) diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 124681f2..012e9dba 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -1,6 +1,6 @@ import datetime from logging import getLogger -from typing import List, Dict, Union, Set +from typing import List, Dict, Union, Set, Optional from PyQt5.QtCore import QThread, pyqtSignal, QSettings, pyqtSlot from PyQt5.QtWidgets import ( @@ -18,6 +18,7 @@ from rare.components.dialogs.install_dialog import InstallDialog from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget, DlWidget from rare.components.tabs.downloads.download_thread import DownloadThread from rare.lgndr.models.downloading import UIUpdate +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel, InstallQueueItemModel from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab @@ -31,12 +32,13 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): dl_queue: List[InstallQueueItemModel] = [] dl_status = pyqtSignal(int) - def __init__(self, updates: Set): - super(DownloadsTab, self).__init__() + def __init__(self, game_updates: Set[RareGame], parent=None): + super(DownloadsTab, self).__init__(parent=parent) self.setupUi(self) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() - self.active_game: Game = None + + self.active_game: Optional[Game] = None self.analysis = None self.kill_button.clicked.connect(self.stop_download) @@ -54,21 +56,21 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.update_text = QLabel(self.tr("No updates available")) self.update_layout.addWidget(self.update_text) - self.update_text.setVisible(len(updates) == 0) + self.update_text.setVisible(len(game_updates) == 0) - for app_name in updates: - self.add_update(app_name) + for rgame in game_updates: + self.add_update(rgame) self.queue_widget.item_removed.connect(self.queue_item_removed) - self.signals.install_game.connect(self.get_install_options) - self.signals.game_uninstalled.connect(self.queue_item_removed) - self.signals.game_uninstalled.connect(self.remove_update) + self.signals.game.install.connect(self.get_install_options) + self.signals.game.uninstalled.connect(self.queue_item_removed) + self.signals.game.uninstalled.connect(self.remove_update) - self.signals.add_download.connect( + self.signals.download.enqueue_game.connect( lambda app_name: self.add_update(app_name) ) - self.signals.game_uninstalled.connect(self.game_uninstalled) + self.signals.game.uninstalled.connect(self.game_uninstalled) self.reset_infos() @@ -77,17 +79,17 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): w.update_button.setDisabled(False) w.update_with_settings.setDisabled(False) - def add_update(self, app_name: str): - if old_widget := self.update_widgets.get(app_name, False): + def add_update(self, rgame: RareGame): + if old_widget := self.update_widgets.get(rgame.app_name, False): old_widget.deleteLater() - self.update_widgets.pop(app_name) - widget = UpdateWidget(self.core, app_name, self) + self.update_widgets.pop(rgame.app_name) + widget = UpdateWidget(self.core, rgame, self) self.update_layout.addWidget(widget) - self.update_widgets[app_name] = widget + self.update_widgets[rgame.app_name] = widget widget.update_signal.connect(self.get_install_options) if QSettings().value("auto_update", False, bool): self.get_install_options( - InstallOptionsModel(app_name=app_name, update=True, silent=True) + InstallOptionsModel(app_name=rgame.app_name, update=True, silent=True) ) widget.update_button.setDisabled(True) self.update_text.setVisible(False) @@ -145,7 +147,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.analysis = queue_item.download.analysis self.dl_name.setText(self.active_game.app_title) - self.signals.installation_started.emit(self.active_game.app_name) + self.signals.progress.started.emit(self.active_game.app_name) @pyqtSlot(DownloadThread.ReturnStatus) def status(self, result: DownloadThread.ReturnStatus): @@ -179,10 +181,10 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.remove_update(game.app_name) self.signals.send_notification.emit(game.app_title) - self.signals.update_gamelist.emit([game.app_name]) + self.signals.game.installed.emit([game.app_name]) self.signals.update_download_tab_text.emit() - self.signals.installation_finished.emit(True, game.app_name) + self.signals.progress.finished.emit(game.app_name, True) self.reset_infos() @@ -199,7 +201,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): if w := self.update_widgets.get(self.active_game.app_name): w.update_button.setDisabled(False) w.update_with_settings.setDisabled(False) - self.signals.installation_finished.emit(False, self.active_game.app_name) + self.signals.progress.finished.emit(self.active_game.app_name, False) self.active_game = None if self.dl_queue: self.start_installation(self.dl_queue[0]) @@ -214,7 +216,8 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.downloaded.setText("n/a") self.analysis = None - def progress_update(self, ui_update: UIUpdate): + @pyqtSlot(str, UIUpdate) + def progress_update(self, app_name: str, ui_update: UIUpdate): self.progress_bar.setValue( 100 * ui_update.total_downloaded // self.analysis.dl_size ) @@ -226,7 +229,8 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}" ) self.time_left.setText(self.get_time(ui_update.estimated_time_left)) - self.signals.dl_progress.emit( + self.signals.progress.value.emit( + app_name, 100 * ui_update.total_downloaded // self.analysis.dl_size ) @@ -265,14 +269,13 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): class UpdateWidget(QWidget): update_signal = pyqtSignal(InstallOptionsModel) - def __init__(self, core: LegendaryCore, app_name: str, parent): + def __init__(self, core: LegendaryCore, rgame: RareGame, parent): super(UpdateWidget, self).__init__(parent=parent) self.core = core - self.game: Game = core.get_game(app_name) - self.igame: InstalledGame = self.core.get_installed_game(app_name) + self.rgame = rgame layout = QVBoxLayout() - self.title = QLabel(self.igame.title) + self.title = QLabel(self.rgame.app_title) layout.addWidget(self.title) self.update_button = QPushButton(self.tr("Update Game")) @@ -284,7 +287,7 @@ class UpdateWidget(QWidget): layout.addWidget( QLabel( self.tr("Version: {} >> {}") - .format(self.igame.version, self.game.app_version(self.igame.platform)) + .format(self.rgame.version, self.rgame.remote_version) ) ) @@ -294,5 +297,5 @@ class UpdateWidget(QWidget): self.update_button.setDisabled(True) self.update_with_settings.setDisabled(True) self.update_signal.emit( - InstallOptionsModel(app_name=self.igame.app_name, update=True, silent=auto) + InstallOptionsModel(app_name=self.rgame.app_name, update=True, silent=auto) ) # True if settings diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/download_thread.py index 6933b2f4..35a43a49 100644 --- a/rare/components/tabs/downloads/download_thread.py +++ b/rare/components/tabs/downloads/download_thread.py @@ -36,7 +36,7 @@ class DownloadThread(QThread): shortcuts: bool = False ret_status = pyqtSignal(ReturnStatus) - ui_update = pyqtSignal(UIUpdate) + ui_update = pyqtSignal(str, UIUpdate) def __init__(self, core: LegendaryCore, item: InstallQueueItemModel): super(DownloadThread, self).__init__() @@ -56,7 +56,8 @@ class DownloadThread(QThread): time.sleep(1) while self.item.download.dlm.is_alive(): try: - self.ui_update.emit(self.item.download.dlm.status_queue.get(timeout=1.0)) + self.ui_update.emit(self.item.download.game.app_name, + self.item.download.dlm.status_queue.get(timeout=1.0)) except queue.Empty: pass if self.dlm_signals.update: diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 61985068..d5800225 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -1,3 +1,4 @@ +import time from logging import getLogger from typing import Tuple, Dict, Union, List, Set @@ -5,6 +6,7 @@ from PyQt5.QtCore import QSettings, Qt, pyqtSlot from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame from legendary.models.game import InstalledGame, Game +from rare.models.game import RareGame from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, @@ -17,13 +19,9 @@ from rare.widgets.sliding_stack import SlidingStackedWidget from .cloud_save_utils import CloudSaveUtils from .game_info import GameInfoTabs from .game_utils import GameUtils -from .game_widgets.base_installed_widget import BaseInstalledWidget -from .game_widgets.base_uninstalled_widget import BaseUninstalledWidget -from .game_widgets.installed_icon_widget import InstalledIconWidget -from .game_widgets.installed_list_widget import InstalledListWidget -from .game_widgets.installing_game_widget import InstallingGameWidget -from .game_widgets.uninstalled_icon_widget import UninstalledIconWidget -from .game_widgets.uninstalled_list_widget import UninstalledListWidget +from .game_widgets import LibraryWidgetController +from .game_widgets.icon_game_widget import IconGameWidget +from .game_widgets.list_game_widget import ListGameWidget from .head_bar import GameListHeadBar from .integrations import IntegrationsTabs @@ -40,17 +38,15 @@ class GamesTab(QStackedWidget): self.image_manager = ImageManagerSingleton() self.settings = QSettings() - self.widgets: Dict[str, Tuple[ - Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = {} - self.updates: Set = set() + self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {} + self.game_updates: Set[RareGame] = set() self.active_filter: int = 0 - self.uninstalled_games: List[Game] = [] self.game_list: List[Game] = self.api_results.game_list - self.dlcs = self.api_results.dlcs - self.bit32 = self.api_results.bit32_games - self.mac_games = self.api_results.mac_games - self.no_assets = self.api_results.no_asset_games + self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs + self.bit32: List[str] = self.api_results.bit32_games + self.mac_games: List[str] = self.api_results.mac_games + self.no_assets: List[Game] = self.api_results.no_asset_games self.game_utils = GameUtils(parent=self) @@ -64,13 +60,10 @@ class GamesTab(QStackedWidget): self.head_bar.goto_eos_ubisoft.connect(self.show_eos_ubisoft) self.games.layout().addWidget(self.head_bar) - self.game_info_tabs = GameInfoTabs(self.dlcs, self.game_utils, self) + self.game_info_tabs = GameInfoTabs(self.game_utils, self) self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games)) self.addWidget(self.game_info_tabs) - self.game_info_tabs.info.verification_finished.connect( - self.verification_finished - ) self.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentWidget(self.games)) self.integrations_tabs = IntegrationsTabs(self) @@ -96,8 +89,6 @@ class GamesTab(QStackedWidget): else: self.no_assets = [] - self.installed = self.core.get_installed_list() - self.view_stack = SlidingStackedWidget(self.games) self.view_stack.setFrameStyle(QFrame.NoFrame) self.icon_view_scroll = QScrollArea(self.view_stack) @@ -116,17 +107,15 @@ class GamesTab(QStackedWidget): self.list_view.setLayout(QVBoxLayout(self.list_view)) self.list_view.layout().setContentsMargins(3, 3, 9, 3) self.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) self.games.layout().addWidget(self.view_stack) - # add installing game widget for icon view: List view not supported - self.installing_widget = InstallingGameWidget() - self.icon_view.layout().addWidget(self.installing_widget) - self.installing_widget.setVisible(False) - if not self.settings.value("icon_view", True, bool): self.view_stack.setCurrentWidget(self.list_view_scroll) self.head_bar.view.list() @@ -145,7 +134,7 @@ class GamesTab(QStackedWidget): ) ) self.head_bar.filterChanged.connect(self.filter_games) - self.head_bar.refresh_list.clicked.connect(self.update_list) + self.head_bar.refresh_list.clicked.connect(self.library_controller.update_list) self.head_bar.view.toggled.connect(self.toggle_view) f = self.settings.value("filter", 0, int) @@ -154,40 +143,16 @@ class GamesTab(QStackedWidget): self.active_filter = self.head_bar.available_filters[f] # signals - self.signals.dl_progress.connect(self.installing_widget.set_status) - self.signals.installation_started.connect(self.installation_started) - self.signals.update_gamelist.connect(self.update_list) - self.signals.installation_finished.connect( - self.installation_finished - ) - self.signals.game_uninstalled.connect(lambda name: self.update_list([name])) + self.signals.game.installed.connect(self.update_count_games_label) + self.signals.game.uninstalled.connect(self.update_count_games_label) + self.signals.game.uninstalled.connect(lambda x: self.setCurrentIndex(0)) - self.game_utils.update_list.connect(self.update_list) + # self.signals.update_gamelist.connect(self.library_controller.update_list) + # self.game_utils.update_list.connect(self.library_controller.update_list) + start_t = time.time() self.setup_game_list() - - def installation_finished(self, app_name: str): - self.installing_widget.setVisible(False) - self.installing_widget.set_game("") - self.filter_games("") - - def installation_started(self, app_name: str): - self.installing_widget.set_game(app_name) - - i_widget, l_widget = self.widgets.get(app_name, (None, None)) - if not i_widget or not l_widget: - return - i_widget.setVisible(False) - l_widget.setVisible(False) - - def verification_finished(self, igame: InstalledGame): - # only if igame needs verification - i_widget, l_widget = self.widgets[igame.app_name] - i_widget.igame = igame - l_widget.igame = igame - - i_widget.leaveEvent(None) - l_widget.update_text() + print(f"Game list setup time: {time.time() - start_t}") @pyqtSlot() def show_import(self): @@ -204,93 +169,55 @@ class GamesTab(QStackedWidget): self.setCurrentWidget(self.integrations_tabs) self.integrations_tabs.show_eos_ubisoft() - @pyqtSlot(str) - def show_game_info(self, app_name): - self.game_info_tabs.update_game(app_name) + @pyqtSlot(RareGame) + def show_game_info(self, rgame): + self.game_info_tabs.update_game(rgame) self.setCurrentWidget(self.game_info_tabs) @pyqtSlot() def update_count_games_label(self): self.head_bar.set_games_count(len(self.core.get_installed_list()), len(self.game_list)) + # FIXME: Remove this when RareCore is in place + def __create_game_with_dlcs(self, game: Game) -> RareGame: + rgame = RareGame(game, self.core, self.image_manager) + if rgame.has_update: + self.game_updates.add(rgame) + if game_dlcs := self.dlcs[rgame.game.catalog_item_id]: + for dlc in game_dlcs: + rdlc = RareGame(dlc, self.core, self.image_manager) + if rdlc.has_update: + self.game_updates.add(rdlc) + rdlc.set_pixmap() + rgame.owned_dlcs.append(rdlc) + return rgame + def setup_game_list(self): self.update_count_games_label() - - # add installed games - for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title): - icon_widget, list_widget = self.add_installed_widget(igame.app_name) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - - for game in self.no_assets: - if not game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin": - icon_widget, list_widget = self.add_uninstalled_widget(game) - else: - icon_widget, list_widget = self.add_installed_widget(game.app_name) + for game in self.game_list + self.no_assets: + rgame = self.__create_game_with_dlcs(game) + icon_widget, list_widget = self.add_library_widget(rgame) if not icon_widget or not list_widget: - logger.warning(f"Ignoring {game.app_name} in game list") + logger.warning(f"Excluding {rgame.app_name} from the game list") continue self.icon_view.layout().addWidget(icon_widget) self.list_view.layout().addWidget(list_widget) - - for game in sorted(self.game_list, key=lambda x: x.app_title): - if not self.core.is_installed(game.app_name): - self.uninstalled_games.append(game) - icon_widget, list_widget = self.add_uninstalled_widget(game) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) + rgame.set_pixmap() self.filter_games(self.active_filter) - def add_installed_widget(self, app_name): - pixmap = self.image_manager.get_pixmap(app_name) + def add_library_widget(self, rgame: RareGame): try: - if pixmap.isNull(): - logger.info(f"{app_name} has a corrupt image.") - if app_name in self.no_asset_names and self.core.get_asset(app_name).namespace != "ue": - self.image_manager.download_image_blocking(self.core.get_game(app_name), force=True) - pixmap = self.image_manager.get_pixmap(app_name) - elif self.ue_name: - pixmap = self.image_manager.get_pixmap(self.ue_name) - - icon_widget = InstalledIconWidget(app_name, pixmap, self.game_utils) - list_widget = InstalledListWidget(app_name, pixmap, self.game_utils) + icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils, self) except Exception as e: - logger.error(f"{app_name} is broken. Don't add it to game list: {e}") + logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}") return None, None - - self.widgets[app_name] = (icon_widget, list_widget) - + self.widgets[rgame.app_name] = (icon_widget, list_widget) icon_widget.show_info.connect(self.show_game_info) list_widget.show_info.connect(self.show_game_info) - - if icon_widget.update_available: - self.updates.add(app_name) - return icon_widget, list_widget - - def add_uninstalled_widget(self, game): - pixmap = self.image_manager.get_pixmap(game.app_name, color=False) - try: - if pixmap.isNull(): - if self.core.get_asset(game.app_name).namespace != "ue": - logger.warning(f"{game.app_title} has a corrupt image. Reloading...") - self.image_manager.download_image_blocking(game, force=True) - pixmap = self.image_manager.get_pixmap(game.app_name, color=False) - elif self.ue_name: - pixmap = self.image_manager.get_pixmap(self.ue_name, color=False) - - icon_widget = UninstalledIconWidget(game, self.core, pixmap) - list_widget = UninstalledListWidget(self.core, game, pixmap) - except Exception as e: - logger.error(f"{game.app_name} is broken. Don't add it to game list: {e}") - return None, None - - icon_widget.show_info.connect(self.show_game_info) - list_widget.show_info.connect(self.show_game_info) - - self.widgets[game.app_name] = (icon_widget, list_widget) - return icon_widget, list_widget + @pyqtSlot(str) + @pyqtSlot(str, str) def filter_games(self, filter_name="all", search_text: str = ""): if not search_text and (t := self.head_bar.search_bar.text()): search_text = t @@ -300,177 +227,7 @@ class GamesTab(QStackedWidget): if not filter_name and (t := self.active_filter): filter_name = t - def get_visibility(widget) -> Tuple[bool, float]: - app_name = widget.game.app_name - - if not isinstance(widget, - InstallingGameWidget) and self.installing_widget.game and self.installing_widget.game.app_name == app_name: - visible = False - elif filter_name == "installed": - visible = self.core.is_installed(app_name) - elif filter_name == "offline": - if self.core.is_installed(app_name) and not isinstance(widget, InstallingGameWidget): - visible = widget.igame.can_run_offline - else: - visible = False - elif filter_name == "32bit" and self.bit32: - visible = app_name in self.bit32 - elif filter_name == "mac" and self.mac_games: - visible = app_name in self.mac_games - elif filter_name == "installable": - visible = app_name not in self.no_asset_names - elif filter_name == "include_ue": - visible = True - elif filter_name == "all": - # All visible except ue - try: - visible = self.core.get_asset(app_name, update=False).namespace != "ue" - except ValueError: - visible = True - else: - visible = True - - if ( - search_text.lower() not in widget.game.app_name.lower() - and search_text.lower() not in widget.game.app_title.lower() - ): - opacity = 0.25 - else: - opacity = 1.0 - return visible, opacity - - for t in self.widgets.values(): - visible, opacity = get_visibility(t[0]) - for w in t: - w.setVisible(visible) - w.image.setOpacity(opacity) - - self.sort_list(search_text) - - if self.installing_widget.game: - self.installing_widget.setVisible(get_visibility(self.installing_widget)[0]) - - @pyqtSlot() - def sort_list(self, sort_by: str = ""): - # lk: this is the existing sorting implemenation - # lk: it sorts by installed then by title - installing_widget = self.icon_view.layout().remove(type(self.installing_widget).__name__) - if sort_by: - self.icon_view.layout().sort(lambda x: (sort_by.lower() not in x.widget().game.app_title.lower(),)) - else: - self.icon_view.layout().sort( - lambda x: ( - not x.widget().is_installed, - x.widget().is_non_asset, - x.widget().app_title, - ) - ) - self.icon_view.layout().insert(0, installing_widget) - list_widgets = self.list_view.findChildren(InstalledListWidget) + self.list_view.findChildren( - UninstalledListWidget) - if sort_by: - list_widgets.sort(key=lambda x: (sort_by not in x.game.app_title.lower(),)) - else: - list_widgets.sort( - key=lambda x: (not x.is_installed, x.is_non_asset, x.app_title) - ) - for idx, wl in enumerate(list_widgets): - self.list_view.layout().insertWidget(idx, wl) - - def __remove_from_layout(self, app_name): - self.icon_view.layout().removeWidget(self.widgets[app_name][0]) - self.list_view.layout().removeWidget(self.widgets[app_name][1]) - self.widgets[app_name][0].deleteLater() - self.widgets[app_name][1].deleteLater() - self.widgets.pop(app_name) - - def __update_installed(self, app_name): - self.__remove_from_layout(app_name) - icon_widget, list_widget = self.add_installed_widget(app_name) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - - def __update_uninstalled(self, app_name): - self.__remove_from_layout(app_name) - game = self.core.get_game(app_name, False) - try: - icon_widget, list_widget = self.add_uninstalled_widget(game) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - except Exception: - pass - - def update_list(self, app_names: list = None): - logger.debug(f"Updating list for {app_names}") - if app_names: - update_list = False - for app_name in app_names: - if widgets := self.widgets.get(app_name): - - # from update - if self.core.is_installed(widgets[0].game.app_name) and isinstance( - widgets[0], BaseInstalledWidget - ): - logger.debug(f"Update Gamelist: Updated: {app_name}") - igame = self.core.get_installed_game(app_name) - for w in widgets: - w.igame = igame - w.update_available = ( - self.core.get_asset( - w.game.app_name, w.igame.platform, False - ).build_version - != igame.version - ) - widgets[0].leaveEvent(None) - # new installed - elif self.core.is_installed(app_name) and isinstance( - widgets[0], BaseUninstalledWidget - ): - logger.debug(f"Update Gamelist: New installed {app_name}") - self.__update_installed(app_name) - update_list = True - - # uninstalled - elif not self.core.is_installed( - widgets[0].game.app_name - ) and isinstance(widgets[0], BaseInstalledWidget): - logger.debug(f"Update list: Uninstalled: {app_name}") - self.__update_uninstalled(app_name) - update_list = True - - # do not update, if only update finished - if update_list: - self.sort_list() - - else: - installed_names = [i.app_name for i in self.core.get_installed_list()] - # get Uninstalled games - uninstalled_names = [] - games = self.core.get_game_list(True) - for game in sorted(games, key=lambda x: x.app_title): - if not game.app_name in installed_names: - uninstalled_names.append(game.app_name) - - new_installed_games = list( - set(installed_names) - set([i.app_name for i in self.installed]) - ) - new_uninstalled_games = list( - set(uninstalled_names) - - set([i.app_name for i in self.uninstalled_games]) - ) - - if (not new_uninstalled_games) and (not new_installed_games): - return - - if new_installed_games: - for name in new_installed_games: - self.__update_installed(name) - - for name in new_uninstalled_games: - self.__update_uninstalled(name) - - self.sort_list() - self.update_count_games_label() + self.library_controller.filter_list(filter_name, search_text.lower()) def toggle_view(self): self.settings.setValue("icon_view", not self.head_bar.view.isChecked()) diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py index 2c5b0ac6..f1a967e1 100644 --- a/rare/components/tabs/games/game_info/__init__.py +++ b/rare/components/tabs/games/game_info/__init__.py @@ -1,11 +1,11 @@ from typing import Optional -from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QTreeView -from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.utils.extra_widgets import SideTabWidget from rare.utils.json_formatter import QJsonModel @@ -15,7 +15,7 @@ from .game_settings import GameSettings class GameInfoTabs(SideTabWidget): - def __init__(self, dlcs: dict, game_utils: GameUtils, parent=None): + def __init__(self, game_utils: GameUtils, parent=None): super(GameInfoTabs, self).__init__(show_back=True, parent=parent) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() @@ -26,8 +26,7 @@ class GameInfoTabs(SideTabWidget): self.settings = GameSettings(self) self.addTab(self.settings, self.tr("Settings")) - self.dlc_list = dlcs - self.dlc = GameDlc(self.dlc_list, game_utils, self) + self.dlc = GameDlc(game_utils, self) self.addTab(self.dlc, self.tr("Downloadable Content")) self.view = GameMetadataView() @@ -35,20 +34,16 @@ class GameInfoTabs(SideTabWidget): self.tabBar().setCurrentIndex(1) - def update_game(self, app_name: str): - installed = bool(self.core.get_installed_game(app_name)) + def update_game(self, rgame: RareGame): + self.info.update_game(rgame) - self.info.update_game(app_name) + self.settings.load_settings(rgame) + self.settings.setEnabled(rgame.is_installed) - self.settings.load_settings(app_name) - self.settings.setEnabled(installed) + self.dlc.update_dlcs(rgame) + self.dlc.setEnabled(bool(rgame.owned_dlcs)) - self.dlc.setEnabled( - not bool(len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) == 0) - ) - self.dlc.update_dlcs(app_name) - - self.view.update_game(self.core.get_game(app_name)) + self.view.update_game(rgame) self.setCurrentIndex(1) @@ -64,14 +59,14 @@ class GameMetadataView(QTreeView): self.setWordWrap(True) self.model = QJsonModel() self.setModel(self.model) - self.game: Optional[Game] = None + self.rgame: Optional[RareGame] = None - def update_game(self, game: Game): - self.game = game - self.title.setTitle(self.game.app_title) + def update_game(self, rgame: RareGame): + self.rgame = rgame + self.title.setTitle(self.rgame.app_title) self.model.clear() try: - self.model.load(game.__dict__) - except: + self.model.load(rgame.game.__dict__) + except Exception as e: pass self.resizeColumnToContents(0) diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py index cc8b06eb..d300f54d 100644 --- a/rare/components/tabs/games/game_info/game_dlc.py +++ b/rare/components/tabs/games/game_info/game_dlc.py @@ -2,11 +2,11 @@ from typing import Optional from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox -from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ImageManagerSingleton +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_dlc import Ui_GameDlc from rare.ui.components.tabs.games.game_info.game_dlc_widget import Ui_GameDlcWidget @@ -16,27 +16,25 @@ from rare.widgets.image_widget import ImageWidget class GameDlc(QWidget): install_dlc = pyqtSignal(str, bool) - def __init__(self, dlcs: dict, game_utils: GameUtils, parent=None): + def __init__(self, game_utils: GameUtils, parent=None): super(GameDlc, self).__init__(parent=parent) self.ui = Ui_GameDlc() self.ui.setupUi(self) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() - self.game: Optional[Game] = None + self.rgame: Optional[RareGame] = None self.game_utils = game_utils self.ui.available_dlc_scroll.setFrameStyle(QFrame.NoFrame) self.ui.installed_dlc_scroll.setFrameStyle(QFrame.NoFrame) - self.dlcs = dlcs self.installed_dlc_widgets = list() self.available_dlc_widgets = list() - def update_dlcs(self, app_name): - self.game = self.core.get_game(app_name) - dlcs = self.dlcs[self.game.catalog_item_id] - self.title.setTitle(self.game.app_title) + def update_dlcs(self, rgame: RareGame): + self.rgame = rgame + self.title.setTitle(self.rgame.app_title) if self.installed_dlc_widgets: for dlc_widget in self.installed_dlc_widgets: @@ -49,8 +47,8 @@ class GameDlc(QWidget): dlc_widget.deleteLater() self.available_dlc_widgets.clear() - for dlc in sorted(dlcs, key=lambda x: x.app_title): - if self.core.is_installed(dlc.app_name): + for dlc in sorted(self.rgame.owned_dlcs, key=lambda x: x.app_title): + if dlc.is_installed: dlc_widget = GameDlcWidget(dlc, True) self.ui.installed_dlc_contents_layout.addWidget(dlc_widget) dlc_widget.uninstall.connect(self.uninstall) @@ -68,34 +66,29 @@ class GameDlc(QWidget): self.ui.available_dlc_label.setVisible(not self.available_dlc_widgets) self.ui.available_dlc_scroll.setVisible(bool(self.available_dlc_widgets)) - @pyqtSlot(str) - def uninstall(self, app_name): - if self.game_utils.uninstall_game(app_name): - self.update_dlcs(self.game.app_name) + @pyqtSlot(RareGame) + def uninstall(self, rgame: RareGame): + if self.game_utils.uninstall_game(rgame.app_name): + self.update_dlcs(self.rgame) def install(self, app_name): - if not self.core.is_installed(self.game.app_name): + if not self.core.is_installed(self.rgame.app_name): QMessageBox.warning( self, "Error", - self.tr("Base Game is not installed. Please install {} first").format( - self.game.app_title - ), + self.tr("Base Game is not installed. Please install {} first").format(self.rgame.app_title), ) return - self.signals.install_game.emit( - InstallOptionsModel(app_name=app_name, update=True) - ) + self.signals.game.install.emit(InstallOptionsModel(app_name=app_name, update=True)) class GameDlcWidget(QFrame): - install = pyqtSignal(str) # Appname - uninstall = pyqtSignal(str) + install = pyqtSignal(RareGame) + uninstall = pyqtSignal(RareGame) - def __init__(self, dlc: Game, installed: bool, parent=None): + def __init__(self, dlc: RareGame, installed: bool, parent=None): super(GameDlcWidget, self).__init__(parent=parent) - self.image_manager = ImageManagerSingleton() self.ui = Ui_GameDlcWidget() self.ui.setupUi(self) self.dlc = dlc @@ -105,10 +98,10 @@ class GameDlcWidget(QFrame): self.ui.dlc_layout.insertWidget(0, self.image) self.ui.dlc_name.setText(dlc.app_title) - self.ui.version.setText(dlc.app_version()) + self.ui.version.setText(dlc.version) self.ui.app_name.setText(dlc.app_name) - self.image.setPixmap(self.image_manager.get_pixmap(dlc.app_name)) + self.image.setPixmap(dlc.pixmap) if installed: self.ui.action_button.setProperty("uninstall", 1) @@ -122,9 +115,9 @@ class GameDlcWidget(QFrame): def uninstall_dlc(self): self.ui.action_button.setDisabled(True) self.ui.action_button.setText(self.tr("Uninstalling")) - self.uninstall.emit(self.dlc.app_name) + self.uninstall.emit(self.dlc) def install_game(self): self.ui.action_button.setDisabled(True) self.ui.action_button.setText(self.tr("Installing")) - self.install.emit(self.dlc.app_name) + self.install.emit(self.dlc) diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 91e19187..75663d21 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -23,6 +23,7 @@ from PyQt5.QtWidgets import ( ) from legendary.models.game import Game, InstalledGame +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel from rare.shared import ( LegendaryCoreSingleton, @@ -313,12 +314,13 @@ class GameInfo(QWidget, Ui_GameInfo): def show_menu_after_browse(self): self.move_button.showMenu() - def update_game(self, app_name: str): - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(self.game.app_name) + def update_game(self, rgame: RareGame): + # FIXME: Use RareGame for the rest of the code + self.game = rgame.game + self.igame = rgame.igame self.title.setTitle(self.game.app_title) - self.image.setPixmap(self.image_manager.get_pixmap(self.game.app_name, color=True)) + self.image.setPixmap(rgame.pixmap) self.app_name.setText(self.game.app_name) if self.igame: @@ -361,7 +363,7 @@ class GameInfo(QWidget, Ui_GameInfo): self.game_actions_stack.setCurrentIndex(0) try: - is_ue = self.core.get_asset(app_name).namespace == "ue" + is_ue = self.core.get_asset(rgame.app_name).namespace == "ue" except ValueError: is_ue = False grade_visible = not is_ue and platform.system() != "Windows" @@ -403,5 +405,5 @@ class GameInfo(QWidget, Ui_GameInfo): self.move_button.setEnabled(False) self.verify_button.setEnabled(False) - self.move_game_pop_up.update_game(app_name) + self.move_game_pop_up.update_game(rgame.app_name) diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py index 3a3628ab..6f44ae15 100644 --- a/rare/components/tabs/games/game_info/game_settings.py +++ b/rare/components/tabs/games/game_info/game_settings.py @@ -8,6 +8,7 @@ from legendary.models.game import Game, InstalledGame from rare.components.tabs.settings import DefaultGameSettings from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings +from rare.models.game import RareGame from rare.shared.workers.wine_resolver import WineResolver from rare.utils import config_helper from rare.utils.extra_widgets import PathEdit @@ -133,11 +134,13 @@ class GameSettings(DefaultGameSettings): config_helper.remove_option(self.game.app_name, option) config_helper.save_config() - def load_settings(self, app_name): + def load_settings(self, rgame: RareGame): self.change = False + # FIXME: Use RareGame for the rest of the code + app_name = rgame.app_name super(GameSettings, self).load_settings(app_name) - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(self.game.app_name) + self.game = rgame.game + self.igame = rgame.igame if self.igame: if self.igame.can_run_offline: offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset") diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py index e69de29b..2e1928d7 100644 --- a/rare/components/tabs/games/game_widgets/__init__.py +++ b/rare/components/tabs/games/game_widgets/__init__.py @@ -0,0 +1,200 @@ +from typing import Tuple, List, Union, Optional + +from PyQt5.QtCore import QObject, pyqtSlot +from PyQt5.QtWidgets import QWidget + +from rare.components.tabs.games.game_utils import GameUtils +from rare.lgndr.core import LegendaryCore +from rare.models.game import RareGame +from rare.models.signals import GlobalSignals +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton +from .icon_game_widget import IconGameWidget +from .list_game_widget import 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.core: LegendaryCore = LegendaryCoreSingleton() + self.signals: GlobalSignals = GlobalSignalsSingleton() + self.api_results = ApiResultsSingleton() + + self.signals.progress.started.connect(self.start_progress) + self.signals.progress.value.connect(self.update_progress) + self.signals.progress.finished.connect(self.finish_progress) + + self.signals.game.installed.connect(self.on_install) + self.signals.game.uninstalled.connect(self.on_uninstall) + self.signals.game.verified.connect(self.on_verified) + + def add_game(self, rgame: RareGame, game_utils: GameUtils, parent): + return self.add_widgets(rgame, game_utils, parent) + + def add_widgets(self, rgame: RareGame, game_utils: GameUtils, parent) -> Tuple[IconGameWidget, ListGameWidget]: + icon_widget = IconGameWidget(rgame, game_utils, parent) + list_widget = ListGameWidget(rgame, game_utils, parent) + return icon_widget, list_widget + + 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 + + @staticmethod + def __visibility(widget, filter_name, search_text) -> Tuple[bool, float]: + if filter_name == "installed": + visible = widget.rgame.is_installed + elif filter_name == "offline": + visible = widget.rgame.can_run_offline + elif filter_name == "32bit": + visible = widget.rgame.is_win32 + elif filter_name == "mac": + visible = widget.rgame.is_mac + elif filter_name == "installable": + visible = not widget.rgame.is_non_asset + elif filter_name == "include_ue": + visible = True + elif filter_name == "all": + visible = not widget.rgame.is_unreal + else: + visible = True + + if ( + search_text not in widget.rgame.app_name.lower() + and search_text not in widget.rgame.app_title.lower() + ): + opacity = 0.25 + else: + opacity = 1.0 + + return visible, opacity + + def filter_list(self, filter_name="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, filter_name, search_text) + iw.setOpacity(opacity) + iw.setVisible(visibility) + for lw in list_widgets: + visibility, opacity = self.__visibility(lw, filter_name, search_text) + lw.setOpacity(opacity) + lw.setVisible(visibility) + self.sort_list(search_text) + + @pyqtSlot() + def sort_list(self, sort_by: str = ""): + # lk: this is the existing sorting implemenation + # lk: it sorts by installed then by title + if sort_by: + self._icon_container.layout().sort(lambda x: (sort_by not in x.widget().rgame.app_title.lower(),)) + else: + self._icon_container.layout().sort( + lambda x: ( + not x.widget().rgame.is_installed, + x.widget().rgame.is_non_asset, + x.widget().rgame.app_title, + ) + ) + list_widgets = self._list_container.findChildren(ListGameWidget) + if sort_by: + list_widgets.sort(key=lambda x: (sort_by not in x.rgame.app_title.lower(),)) + else: + 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) + + @pyqtSlot() + @pyqtSlot(list) + def update_list(self, app_names: List[str] = None): + if not app_names: + # 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 = set([iw.rgame.app_name for iw in icon_widgets]) + list_app_names = set([lw.rgame.app_name for lw in list_widgets]) + games = self.api_results.game_list + self.api_results.no_asset_games + game_app_names = set([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.rare_core.get_game(app_name) + iw = IconGameWidget(game) + self._icon_container.layout().addWidget(iw) + for app_name in new_list_app_names: + game = self.rare_core.get_game(app_name) + lw = ListGameWidget(game) + self._list_container.layout().addWidget(lw) + self.sort_list() + + @pyqtSlot(list) + def on_install(self, app_names: List[str]): + for app_name in app_names: + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.set_installed(True) + if lw is not None: + lw.rgame.set_installed(True) + self.sort_list() + + @pyqtSlot(str) + def on_uninstall(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.set_installed(False) + if lw is not None: + lw.rgame.set_installed(False) + self.sort_list() + + @pyqtSlot(str) + def on_verified(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.needs_verification = False + if lw is not None: + lw.rgame.needs_verification = False + + # lk: this should go in downloads and happen once + def __find_game_for_dlc(self, app_name: str) -> Optional[str]: + game = self.core.get_game(app_name, False) + # lk: how can an app_name not refer to a game? + if not game: + return None + if game.is_dlc: + game_list = self.core.get_game_list(update_assets=False) + game = list( + filter( + lambda x: x.asset_infos["Windows"].catalog_item_id == game.metadata["mainGameItem"]["id"], + game_list, + ) + ) + return game[0].app_name + return app_name + + @pyqtSlot(str) + def start_progress(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.start_progress() + if lw is not None: + lw.rgame.start_progress() + + @pyqtSlot(str, int) + def update_progress(self, app_name: str, progress: int): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.update_progress(progress) + if lw is not None: + lw.rgame.update_progress(progress) + + @pyqtSlot(str, bool) + def finish_progress(self, app_name: str, stopped: bool): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.finish_progress(not stopped, 0, "") + if lw is not None: + lw.rgame.finish_progress(not stopped, 0, "") diff --git a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py deleted file mode 100644 index a4147fff..00000000 --- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py +++ /dev/null @@ -1,72 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtWidgets import QFrame, QAction -from legendary.models.game import Game - -from rare.shared import ImageManagerSingleton -from rare.shared.image_manager import ImageSize -from rare.widgets.image_widget import ImageWidget - -logger = getLogger("Uninstalled") - - -class BaseUninstalledWidget(QFrame): - show_info = pyqtSignal(str) - - def __init__(self, game, core, pixmap): - super(BaseUninstalledWidget, self).__init__() - self.image_manager = ImageManagerSingleton() - - self.game = game - if self.game.app_title == "Unreal Engine": - self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}" - - self.core = core - self.image = ImageWidget(self) - self.image.setFixedSize(ImageSize.Display) - self.image.setPixmap(pixmap) - self.installing = False - self.setContextMenuPolicy(Qt.ActionsContextMenu) - - reload_image = QAction(self.tr("Reload Image"), self) - reload_image.triggered.connect(self.reload_image) - self.addAction(reload_image) - - def reload_image(self): - self.image_manager.download_image_blocking(self.game, force=True) - pm = self.image_manager.get_pixmap(self.game.app_name, color=False) - self.image.setPixmap(pm) - - def install(self): - self.show_info.emit(self.game.app_name) - - # From RareGame, added from sorting to work - @property - def is_non_asset(self) -> bool: - """! - @brief Property to report if a Game doesn't have assets - - Typically, games have assets, however some games that require - other launchers do not have them. Rare treats these games as installed - offering to execute their launcher. - - @return bool If the game doesn't have assets - """ - return not self.game.asset_infos - - @property - def is_installed(self) -> bool: - """! - @brief Property to report if a game is installed - - This returns True if InstalledGame data have been loaded for the game - or if the game is a game without assets, for example an Origin game. - - @return bool If the game should be considered installed - """ - return False or self.is_non_asset - - @property - def app_title(self) -> str: - return self.game.app_title diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/game_widget.py similarity index 55% rename from rare/components/tabs/games/game_widgets/base_installed_widget.py rename to rare/components/tabs/games/game_widgets/game_widget.py index 6281595a..0f0941d7 100644 --- a/rare/components/tabs/games/game_widgets/base_installed_widget.py +++ b/rare/components/tabs/games/game_widgets/game_widget.py @@ -1,40 +1,124 @@ import os import platform from logging import getLogger +from typing import Optional -from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QFrame, QMessageBox, QAction +from PyQt5.QtCore import pyqtSignal, QSettings, QStandardPaths, Qt +from PyQt5.QtGui import QMouseEvent +from PyQt5.QtWidgets import QMessageBox, QAction +from legendary.models.game import Game, InstalledGame from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton -from rare.shared.image_manager import ImageSize +from rare.models.game import RareGame +from rare.shared import ( + LegendaryCoreSingleton, + GlobalSignalsSingleton, + ArgumentsSingleton, + ImageManagerSingleton, +) from rare.utils.misc import create_desktop_link -from rare.widgets.image_widget import ImageWidget +from .library_widget import LibraryWidget -logger = getLogger("Game") +logger = getLogger("BaseGameWidget") -class BaseInstalledWidget(QFrame): - launch_signal = pyqtSignal(str, QProcess, list) - show_info = pyqtSignal(str) - finish_signal = pyqtSignal(str, int) - proc: QProcess() +class GameWidget(LibraryWidget): + show_info = pyqtSignal(RareGame) - def __init__(self, app_name, pixmap: QPixmap, game_utils: GameUtils): - super(BaseInstalledWidget, self).__init__() + def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None): + super(GameWidget, self).__init__(parent=parent) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() self.args = ArgumentsSingleton() self.image_manager = ImageManagerSingleton() + self.game_utils = game_utils + self.rgame = rgame + self.rgame.signals.widget.update.connect( + lambda: self.setPixmap(self.rgame.pixmap) + ) + self.rgame.signals.progress.start.connect( + lambda: self.showProgress( + self.image_manager.get_pixmap(self.rgame.app_name, True), + self.image_manager.get_pixmap(self.rgame.app_name, False) + ) + ) + self.rgame.signals.progress.update.connect( + lambda p: self.updateProgress(p) + ) + self.rgame.signals.progress.finish.connect( + lambda e: self.hideProgress(e) + ) + + self.rgame: RareGame = rgame self.syncing_cloud_saves = False + self.game_running = False + + self.settings = QSettings() + + self.installing = False + self.setContextMenuPolicy(Qt.ActionsContextMenu) + launch = QAction(self.tr("Launch"), self) + launch.triggered.connect(self.launch) + self.addAction(launch) + + if self.rgame.game.supports_cloud_saves: + sync = QAction(self.tr("Sync with cloud"), self) + sync.triggered.connect(self.sync_game) + self.addAction(sync) + + desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) + if os.path.exists( + os.path.join(desktop, f"{self.rgame.app_title}.desktop") + ) or os.path.exists(os.path.join(desktop, f"{self.rgame.app_title}.lnk")): + self.create_desktop = QAction(self.tr("Remove Desktop link")) + else: + self.create_desktop = QAction(self.tr("Create Desktop link")) + if self.rgame.is_installed: + self.create_desktop.triggered.connect( + lambda: self.create_desktop_link("desktop") + ) + self.addAction(self.create_desktop) + + applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) + if platform.system() == "Linux": + start_menu_file = os.path.join(applications, f"{self.rgame.app_title}.desktop") + elif platform.system() == "Windows": + start_menu_file = os.path.join(applications, "..", f"{self.rgame.app_title}.lnk") + else: + start_menu_file = "" + if platform.system() in ["Windows", "Linux"]: + if os.path.exists(start_menu_file): + self.create_start_menu = QAction(self.tr("Remove start menu link")) + else: + self.create_start_menu = QAction(self.tr("Create start menu link")) + if self.rgame.is_installed: + self.create_start_menu.triggered.connect( + lambda: self.create_desktop_link("start_menu") + ) + self.addAction(self.create_start_menu) + + reload_image = QAction(self.tr("Reload Image"), self) + reload_image.triggered.connect(self.reload_image) + self.addAction(reload_image) + + if not self.rgame.is_origin: + uninstall = QAction(self.tr("Uninstall"), self) + self.addAction(uninstall) + uninstall.triggered.connect( + lambda: self.signals.game.uninstalled.emit(self.rgame.app_name) + if self.game_utils.uninstall_game(self.rgame.app_name) + else None + ) + self.texts = { - "needs_verification": self.tr("Please verify game before playing"), + "static": { + "needs_verification": self.tr("Please verify game before playing"), + }, "hover": { - "update_available": self.tr("Start game without version check"), + "update_available": self.tr("Start without version check"), "launch": self.tr("Launch Game"), "launch_origin": self.tr("Launch/Link"), "running": self.tr("Game running"), @@ -49,101 +133,60 @@ class BaseInstalledWidget(QFrame): }, } - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(app_name) # None if origin - - if self.game.app_title == "Unreal Engine": - self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}" - - self.is_only_offline = False - - try: - self.core.get_asset(app_name, platform=self.igame.platform).build_version - except ValueError: - logger.warning(f"Game {self.game.app_title} has no metadata. Set offline true") - self.is_only_offline = True - except AttributeError: - pass - - self.image = ImageWidget(self) - self.image.setFixedSize(ImageSize.Display) - self.image.setPixmap(pixmap) - self.game_running = False - self.offline = self.args.offline - self.update_available = False - if self.igame and self.core.lgd.assets: - try: - remote_version = self.core.get_asset( - self.game.app_name, platform=self.igame.platform, update=False - ).build_version - except ValueError: - logger.error(f"Asset error for {self.game.app_title}") - self.update_available = False + @property + def enterEventText(self) -> str: + if self.rgame.is_installed: + if self.game_running: + return self.texts["hover"]["running"] + elif (not self.rgame.is_origin) and self.rgame.needs_verification: + return self.texts["static"]["needs_verification"] + elif self.rgame.is_foreign: + return self.texts["hover"]["launch_offline"] + elif self.rgame.has_update: + return self.texts["hover"]["update_available"] else: - if remote_version != self.igame.version: - self.update_available = True - - self.data = QByteArray() - self.settings = QSettings() - - self.setContextMenuPolicy(Qt.ActionsContextMenu) - launch = QAction(self.tr("Launch"), self) - launch.triggered.connect(self.launch) - self.addAction(launch) - - if self.game.supports_cloud_saves: - sync = QAction(self.tr("Sync with cloud"), self) - sync.triggered.connect(self.sync_game) - self.addAction(sync) - - desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) - if os.path.exists( - os.path.join(desktop, f"{self.game.app_title}.desktop") - ) or os.path.exists(os.path.join(desktop, f"{self.game.app_title}.lnk")): - self.create_desktop = QAction(self.tr("Remove Desktop link")) + return self.tr("Game Info") + # return self.texts["hover"]["launch" if self.igame else "launch_origin"] else: - self.create_desktop = QAction(self.tr("Create Desktop link")) - if self.igame: - self.create_desktop.triggered.connect( - lambda: self.create_desktop_link("desktop") - ) - self.addAction(self.create_desktop) - - applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) - if platform.system() == "Linux": - start_menu_file = os.path.join(applications, f"{self.game.app_title}.desktop") - elif platform.system() == "Windows": - start_menu_file = os.path.join(applications, "..", f"{self.game.app_title}.lnk") - else: - start_menu_file = "" - if platform.system() in ["Windows", "Linux"]: - if os.path.exists(start_menu_file): - self.create_start_menu = QAction(self.tr("Remove start menu link")) + if not self.installing: + return self.tr("Game Info") else: - self.create_start_menu = QAction(self.tr("Create start menu link")) - if self.igame: - self.create_start_menu.triggered.connect( - lambda: self.create_desktop_link("start_menu") - ) - self.addAction(self.create_start_menu) + return self.tr("Installation running") - reload_image = QAction(self.tr("Reload Image"), self) - reload_image.triggered.connect(self.reload_image) - self.addAction(reload_image) + @property + def leaveEventText(self) -> str: + if self.rgame.is_installed: + if self.game_running: + return self.texts["default"]["running"] + elif self.syncing_cloud_saves: + return self.texts["default"]["syncing"] + elif self.rgame.is_foreign: + return self.texts["default"]["no_meta"] + elif self.rgame.has_update: + return self.texts["default"]["update_available"] + elif (not self.rgame.is_origin) and self.rgame.needs_verification: + return self.texts["static"]["needs_verification"] + else: + return "" + else: + if self.installing: + return "Installation..." + else: + return "" - if self.igame is not None: - uninstall = QAction(self.tr("Uninstall"), self) - self.addAction(uninstall) - uninstall.triggered.connect( - lambda: self.signals.update_gamelist.emit([self.game.app_name]) - if self.game_utils.uninstall_game(self.game.app_name) - else None - ) + def mousePressEvent(self, e: QMouseEvent) -> None: + # left button + if e.button() == 1: + self.show_info.emit(self.rgame) + # right + elif e.button() == 2: + pass # self.showMenu(e) - def reload_image(self): - self.image_manager.download_image_blocking(self.game, force=True) - pm = self.image_manager.get_pixmap(self.game.app_name, color=True) - self.image.setPixmap(pm) + def reload_image(self) -> None: + self.rgame.refresh_pixmap() + + def install(self): + self.show_info.emit(self.rgame) def create_desktop_link(self, type_of_link): if type_of_link == "desktop": @@ -154,9 +197,9 @@ class BaseInstalledWidget(QFrame): return if platform.system() == "Windows": - shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.lnk") + shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.lnk") elif platform.system() == "Linux": - shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.desktop") + shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.desktop") else: QMessageBox.warning( self, @@ -167,7 +210,7 @@ class BaseInstalledWidget(QFrame): if not os.path.exists(shortcut_path): try: - if not create_desktop_link(self.game.app_name, self.core, type_of_link): + if not create_desktop_link(self.rgame.app_name, self.core, type_of_link): return except PermissionError: QMessageBox.warning( @@ -189,16 +232,16 @@ class BaseInstalledWidget(QFrame): def launch(self, offline=False, skip_version_check=False): if self.game_running: return - offline = offline or self.is_only_offline - if self.is_only_offline and not self.igame.can_run_offline: + offline = offline or self.rgame.is_foreign + if self.rgame.is_foreign and not self.rgame.can_run_offline: QMessageBox.warning(self, "Warning", self.tr("This game is probably not in your library and it cannot be launched offline")) return - if self.game.supports_cloud_saves and not offline: + if self.rgame.game.supports_cloud_saves and not offline: self.syncing_cloud_saves = True self.game_utils.prepare_launch( - self.game.app_name, offline, skip_version_check + self.rgame, offline, skip_version_check ) def sync_finished(self, app_name): @@ -207,7 +250,7 @@ class BaseInstalledWidget(QFrame): def sync_game(self): try: sync = self.game_utils.cloud_save_utils.sync_before_launch_game( - self.game.app_name, True + self.rgame.app_name, True ) except Exception: sync = False @@ -218,37 +261,3 @@ class BaseInstalledWidget(QFrame): if error: QMessageBox.warning(self, "Error", error) self.game_running = False - - # From RareGame, added from sorting to work - @property - def is_non_asset(self) -> bool: - """! - @brief Property to report if a Game doesn't have assets - - Typically, games have assets, however some games that require - other launchers do not have them. Rare treats these games as installed - offering to execute their launcher. - - @return bool If the game doesn't have assets - """ - return not self.game.asset_infos - - @property - def is_origin(self) -> bool: - return self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin" - - @property - def is_installed(self) -> bool: - """! - @brief Property to report if a game is installed - - This returns True if InstalledGame data have been loaded for the game - or if the game is a game without assets, for example an Origin game. - - @return bool If the game should be considered installed - """ - return (self.igame is not None) or self.is_non_asset - - @property - def app_title(self) -> str: - return self.igame.title if self.igame is not None else self.game.app_title diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py new file mode 100644 index 00000000..ffbc6984 --- /dev/null +++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py @@ -0,0 +1,77 @@ +from logging import getLogger + +from PyQt5.QtCore import QEvent, pyqtSignal + +from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame +from rare.shared.image_manager import ImageSize +from .game_widget import GameWidget +from .icon_widget import IconWidget + +logger = getLogger("IconGameWidget") + + +class IconGameWidget(GameWidget): + is_ready = False + update_game = pyqtSignal() + + def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None): + super(IconGameWidget, self).__init__(rgame, game_utils, parent) + self.setObjectName(f"{rgame.app_name}") + self.setFixedSize(ImageSize.Display) + self.ui = IconWidget() + self.ui.setupUi(self) + + self.game_utils.finished.connect(self.game_finished) + + self.ui.title_label.setText(f"

{self.rgame.app_title}

") + self.ui.launch_btn.clicked.connect(self.game_launch) + self.ui.launch_btn.setVisible(self.rgame.is_installed) + self.ui.install_btn.clicked.connect(self.install) + self.ui.install_btn.setVisible(not self.rgame.is_installed) + + if self.rgame.igame and self.rgame.needs_verification: + self.ui.status_label.setText(self.texts["static"]["needs_verification"]) + + self.game_utils.game_launched.connect(self.game_started) + + self.is_ready = True + self.ui.launch_btn.setEnabled(self.rgame.can_launch) + + def enterEvent(self, a0: QEvent = None) -> None: + if a0 is not None: + a0.accept() + self.ui.status_label.setText(self.enterEventText) + self.ui.enterAnimation(self) + + def leaveEvent(self, a0: QEvent = None) -> None: + if a0 is not None: + a0.accept() + self.ui.leaveAnimation(self) + self.ui.status_label.setText(self.leaveEventText) + + def game_launch(self): + if not self.game_running: + if self.rgame.igame and self.rgame.needs_verification: + return + if self.rgame.has_update: + self.launch(skip_version_check=True) + else: + self.launch() + + def sync_finished(self, app_name): + if not app_name == self.rgame.app_name: + return + super().sync_finished(app_name) + self.leaveEvent(None) + + def game_finished(self, app_name, error): + if app_name != self.rgame.app_name: + return + self.game_running = False + self.leaveEvent(None) + + def game_started(self, app_name): + if app_name == self.rgame.app_name: + self.game_running = True + self.leaveEvent(None) diff --git a/rare/components/tabs/games/game_widgets/icon_widget.py b/rare/components/tabs/games/game_widgets/icon_widget.py new file mode 100644 index 00000000..422a28e8 --- /dev/null +++ b/rare/components/tabs/games/game_widgets/icon_widget.py @@ -0,0 +1,160 @@ +from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QSize +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QGraphicsOpacityEffect, + QSpacerItem, + QSizePolicy, + QHBoxLayout, + QLabel, + QPushButton, +) + +from rare.utils.misc import icon +from rare.widgets.elide_label import ElideLabel + + +class IconWidget(object): + _effect = None + _animation = None + + def setupUi(self, widget: QWidget): + # on-hover popup + self.mini_widget = QWidget(parent=widget) + self.mini_widget.setObjectName(f"{type(self).__name__}MiniWidget") + self.mini_widget.setStyleSheet( + f"QWidget#{self.mini_widget.objectName()}" + "{" + "color: rgb(238, 238, 238);" + "background-color: rgba(0, 0, 0, 65%);" + "border-radius: 5%;" + "border-bottom-left-radius: 9%;" + "border-bottom-right-radius: 9%;" + "}" + ) + self.mini_widget.setFixedHeight(widget.height() // 3) + + self.mini_effect = QGraphicsOpacityEffect(self.mini_widget) + self.mini_widget.setGraphicsEffect(self.mini_effect) + + # game title + self.title_label = QLabel(parent=self.mini_widget) + self.title_label.setObjectName(f"{type(self).__name__}TitleLabel") + self.title_label.setStyleSheet( + f"QLabel#{self.title_label.objectName()}" + "{" + "background-color: rgba(0, 0, 0, 0%); color: white;" + "}" + ) + self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.title_label.setAlignment(Qt.AlignTop) + self.title_label.setAutoFillBackground(False) + self.title_label.setWordWrap(True) + + # information below title + self.status_label = ElideLabel(parent=self.mini_widget) + self.status_label.setObjectName(f"{type(self).__name__}StatusLabel") + self.status_label.setStyleSheet( + f"QLabel#{self.status_label.objectName()}" + "{" + "background-color: rgba(0, 0, 0, 0%); color: white;" + "}" + ) + self.status_label.setAutoFillBackground(False) + + # play button + self.launch_btn = QPushButton(parent=self.mini_widget) + self.launch_btn.setObjectName(f"{type(self).__name__}LaunchButton") + self.launch_btn.setStyleSheet( + f"QPushButton#{self.launch_btn.objectName()}" + "{" + "border-radius: 10%;" + "background-color: rgba(0, 0, 0, 65%);" + "border-color: black; border-width: 1px;" + "}" + f"QPushButton#{self.launch_btn.objectName()}::hover" + "{" + "border-color: gray;" + "}" + ) + self.launch_btn.setIcon(icon("ei.play-alt", color="white")) + self.launch_btn.setIconSize(QSize(20, 20)) + self.launch_btn.setFixedSize(QSize(widget.width() // 4, widget.width() // 4)) + + self.install_btn = QPushButton(parent=self.mini_widget) + self.install_btn.setObjectName(f"{type(self).__name__}InstallButton") + self.install_btn.setStyleSheet( + f"QPushButton#{self.install_btn.objectName()}" + "{" + "border-radius: 10%;" + "background-color: rgba(0, 0, 0, 65%);" + "border-color: black; border-width: 1px;" + "}" + f"QPushButton#{self.install_btn.objectName()}::hover" + "{" + "border-color: gray;" + "}" + ) + self.install_btn.setIcon(icon("ri.install-fill", color="white")) + self.install_btn.setIconSize(QSize(20, 20)) + self.install_btn.setFixedSize(QSize(widget.width() // 4, widget.width() // 4)) + + # Create layouts + # layout for the whole widget, holds the image + layout = QVBoxLayout() + layout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSizeConstraint(QVBoxLayout.SetFixedSize) + + # layout for the image, holds the mini widget and a spacer item + image_layout = QVBoxLayout() + image_layout.setContentsMargins(0, 0, 0, 0) + + # layout for the mini widget, holds the top row and the info label + mini_layout = QVBoxLayout() + mini_layout.setSpacing(0) + + # layout for the top row, holds the title and the launch button + row_layout = QHBoxLayout() + row_layout.setSpacing(0) + row_layout.setAlignment(Qt.AlignTop) + + # Layout the widgets + # (from inner to outer) + row_layout.addWidget(self.title_label, stretch=2) + row_layout.addWidget(self.launch_btn) + row_layout.addWidget(self.install_btn) + mini_layout.addLayout(row_layout, stretch=2) + mini_layout.addWidget(self.status_label) + self.mini_widget.setLayout(mini_layout) + + image_layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) + image_layout.addWidget(self.mini_widget) + widget.setLayout(image_layout) + + # layout.addWidget(widget.image) + # widget.setLayout(layout) + + widget.setContextMenuPolicy(Qt.ActionsContextMenu) + widget.leaveEvent(None) + + self.translateUi(widget) + + def translateUi(self, widget: QWidget): + pass + + def enterAnimation(self, widget: QWidget): + self._animation = QPropertyAnimation(self.mini_effect, b"opacity") + self._animation.setDuration(250) + self._animation.setStartValue(0) + self._animation.setEndValue(1) + self._animation.setEasingCurve(QEasingCurve.InSine) + self._animation.start(QPropertyAnimation.DeleteWhenStopped) + + def leaveAnimation(self, widget: QWidget): + self._animation = QPropertyAnimation(self.mini_effect, b"opacity") + self._animation.setDuration(150) + self._animation.setStartValue(1) + self._animation.setEndValue(0) + self._animation.setEasingCurve(QEasingCurve.OutSine) + self._animation.start(QPropertyAnimation.DeleteWhenStopped) diff --git a/rare/components/tabs/games/game_widgets/installed_icon_widget.py b/rare/components/tabs/games/game_widgets/installed_icon_widget.py deleted file mode 100644 index 1513902f..00000000 --- a/rare/components/tabs/games/game_widgets/installed_icon_widget.py +++ /dev/null @@ -1,137 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import QEvent, pyqtSignal, Qt -from PyQt5.QtGui import QMouseEvent -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QWidget - -from rare.components.tabs.games.game_widgets.base_installed_widget import ( - BaseInstalledWidget, -) -from rare.shared import LegendaryCoreSingleton -from rare.shared.image_manager import ImageSize -from rare.utils.misc import icon -from rare.widgets.elide_label import ElideLabel - -logger = getLogger("GameWidgetInstalled") - - -class InstalledIconWidget(BaseInstalledWidget): - update_game = pyqtSignal() - - def __init__(self, app_name, pixmap, game_utils): - super(InstalledIconWidget, self).__init__(app_name, pixmap, game_utils) - self.setObjectName(type(self).__name__) - - self.setContextMenuPolicy(Qt.ActionsContextMenu) - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - self.setFixedWidth(ImageSize.Display.size.width()) - self.core = LegendaryCoreSingleton() - - if self.update_available: - logger.info(f"Update available for game: {self.game.app_name}") - - layout.addWidget(self.image) - - self.game_utils.finished.connect(self.game_finished) - self.game_utils.cloud_save_finished.connect(self.sync_finished) - - miniwidget = QWidget(self) - miniwidget.setFixedWidth(ImageSize.Display.size.width()) - minilayout = QHBoxLayout() - minilayout.setContentsMargins(0, 0, 0, 0) - minilayout.setSpacing(0) - miniwidget.setLayout(minilayout) - - self.title_label = ElideLabel(f"{self.game.app_title}", parent=miniwidget) - self.title_label.setAlignment(Qt.AlignTop) - self.title_label.setObjectName("game_widget") - minilayout.addWidget(self.title_label, stretch=2) - - # Info Button - self.menu_btn = QPushButton(parent=miniwidget) - self.menu_btn.setIcon(icon("ei.info-circle")) - # self.menu_btn.setObjectName("installed_menu_button") - self.menu_btn.enterEvent = lambda x: self.info_label.setText(self.tr("Information")) - self.menu_btn.leaveEvent = lambda x: self.enterEvent(None) - # remove Border - - self.menu_btn.setObjectName("menu_button") - - self.menu_btn.clicked.connect(lambda: self.show_info.emit(self.game.app_name)) - self.menu_btn.setFixedSize(22, 22) - minilayout.addWidget(self.menu_btn, stretch=0) - minilayout.setAlignment(Qt.AlignTop) - layout.addWidget(miniwidget) - - self.info_label = ElideLabel(" ", parent=self) - self.info_label.setFixedWidth(ImageSize.Display.size.width()) - self.leaveEvent(None) - self.info_label.setObjectName("info_label") - layout.addWidget(self.info_label) - - if self.igame and self.igame.needs_verification: - self.info_label.setText(self.texts["needs_verification"]) - - self.setLayout(layout) - - self.game_utils.game_launched.connect(self.game_started) - - def enterEvent(self, a0: QEvent = None) -> None: - if self.game_running: - self.info_label.setText(self.texts["hover"]["running"]) - elif self.igame and self.igame.needs_verification: - self.info_label.setText(self.texts["needs_verification"]) - elif self.is_only_offline: - self.info_label.setText(self.texts["hover"]["launch_offline"]) - elif self.update_available: - self.info_label.setText(self.texts["hover"]["update_available"]) - else: - self.info_label.setText( - self.texts["hover"]["launch" if self.igame else "launch_origin" if self.is_origin else "no_launch"] - ) - - def leaveEvent(self, a0: QEvent = None) -> None: - if self.game_running: - self.info_label.setText(self.texts["default"]["running"]) - elif self.syncing_cloud_saves: - self.info_label.setText(self.texts["default"]["syncing"]) - elif self.is_only_offline: - self.info_label.setText(self.texts["default"]["no_meta"]) - elif self.update_available: - self.info_label.setText(self.texts["default"]["update_available"]) - elif self.igame and self.igame.needs_verification: - self.info_label.setText(self.texts["needs_verification"]) - else: - self.info_label.setText(" ") # invisible text, cheap way to always vertical have size in label - - def mousePressEvent(self, e: QMouseEvent): - # left button - if e.button() == 1 and not self.game_running: - if self.igame and self.igame.needs_verification: - return - if self.update_available: - self.launch(skip_version_check=True) - else: - self.launch() - - # right - elif e.button() == 2: - pass # self.showMenu(e) - - def sync_finished(self, app_name): - if not app_name == self.game.app_name: - return - super().sync_finished(app_name) - self.leaveEvent(None) - - def game_finished(self, app_name, error): - if app_name != self.game.app_name: - return - self.game_running = False - self.leaveEvent(None) - - def game_started(self, app_name): - if app_name == self.game.app_name: - self.game_running = True - self.leaveEvent(None) diff --git a/rare/components/tabs/games/game_widgets/installed_list_widget.py b/rare/components/tabs/games/game_widgets/installed_list_widget.py deleted file mode 100644 index ef178f48..00000000 --- a/rare/components/tabs/games/game_widgets/installed_list_widget.py +++ /dev/null @@ -1,110 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import QProcess, pyqtSignal, Qt -from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout - -from rare.components.tabs.games.game_widgets.base_installed_widget import ( - BaseInstalledWidget, -) -from rare.utils.misc import icon, get_size - -logger = getLogger("GameWidget") - - -class InstalledListWidget(BaseInstalledWidget): - proc: QProcess - signal = pyqtSignal(str) - update_game = pyqtSignal() - - def __init__(self, app_name, pixmap, game_utils): - super(InstalledListWidget, self).__init__(app_name, pixmap, game_utils) - self.setFrameStyle(self.StyledPanel) - self.dev = self.game.metadata["developer"] - if self.igame: - self.size = self.igame.install_size - else: - self.size = 0 - - self.layout = QHBoxLayout() - self.setLayout(self.layout) - self.layout.addWidget(self.image) - - # Layout on the right - self.childLayout = QVBoxLayout() - self.layout.addLayout(self.childLayout) - - play_icon = icon("ei.play") - self.title_label = QLabel(f"

{self.game.app_title}

") - self.title_label.setWordWrap(True) - self.childLayout.addWidget(self.title_label) - self.app_name_label = QLabel(self.game.app_name) - self.launch_button = QPushButton( - play_icon, - self.tr("Launch") if self.igame else self.tr("Link/Play") if self.is_origin else self.texts["hover"][ - "no_launch"] - ) - if not self.is_origin and not self.igame: - self.launch_button.setDisabled(True) - - self.launch_button.setObjectName("launch_game_button") - self.launch_button.setFixedWidth(150) - - self.info = QPushButton("Info") - self.info.clicked.connect(lambda: self.show_info.emit(self.game.app_name)) - self.info.setFixedWidth(80) - - self.info_label = QLabel("") - self.childLayout.addWidget(self.info_label) - self.update_text() - - self.launch_button.clicked.connect(self.launch) - - self.childLayout.addWidget(self.launch_button) - self.childLayout.addWidget(self.info) - self.childLayout.addWidget(self.app_name_label) - self.developer_label = QLabel(self.tr("Developer: {}").format(self.dev)) - self.childLayout.addWidget(self.developer_label) - if self.igame: - self.version_label = QLabel(f"Version: {self.igame.version}") - self.size_label = QLabel( - f"{self.tr('Installed size')}: {get_size(self.size)}" - ) - - self.childLayout.addWidget(self.version_label) - self.childLayout.addWidget(self.size_label) - - self.childLayout.setAlignment(Qt.AlignTop) - self.layout.setAlignment(Qt.AlignLeft) - - self.game_utils.cloud_save_finished.connect(self.sync_finished) - self.game_utils.finished.connect(self.game_finished) - - self.leaveEvent = self.update_text - self.enterEvent = self.update_text - - self.game_utils.game_launched.connect(self.game_started) - - def update_text(self, e=None): - if self.update_available: - self.info_label.setText(self.texts["default"]["update_available"]) - elif self.is_only_offline: - self.info_label.setText(self.texts["default"]["no_meta"]) - elif self.igame and self.igame.needs_verification: - self.info_label.setText(self.texts["needs_verification"]) - elif self.syncing_cloud_saves: - self.info_label.setText(self.texts["default"]["syncing"]) - else: - self.info_label.setText("") - - def game_started(self, app_name): - if app_name == self.game.app_name: - self.game_running = True - self.update_text() - self.launch_button.setDisabled(True) - - def game_finished(self, app_name, error): - if app_name != self.game.app_name: - return - super().game_finished(app_name, error) - self.update_text(None) - self.launch_button.setDisabled(False) diff --git a/rare/components/tabs/games/game_widgets/installing_game_widget.py b/rare/components/tabs/games/game_widgets/installing_game_widget.py deleted file mode 100644 index cc7cf4da..00000000 --- a/rare/components/tabs/games/game_widgets/installing_game_widget.py +++ /dev/null @@ -1,66 +0,0 @@ -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QFrame -from legendary.models.game import Game - -from rare.shared import LegendaryCoreSingleton, ImageManagerSingleton -from rare.shared.image_manager import ImageSize -from rare.widgets.elide_label import ElideLabel -from .library_widget import LibraryWidget - - -class InstallingGameWidget(QFrame): - game: Game = None - - def __init__(self): - super(InstallingGameWidget, self).__init__() - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - self.setFixedWidth(ImageSize.Display.size.width()) - self.setObjectName(type(self).__name__) - - self.core = LegendaryCoreSingleton() - self.image_manager = ImageManagerSingleton() - - self.pixmap = QPixmap() - self.image = LibraryWidget(parent=self) - self.image.setFixedSize(ImageSize.Display) - layout.addWidget(self.image) - - miniwidget = QWidget(self) - miniwidget.setFixedWidth(ImageSize.Display.size.width()) - minilayout = QHBoxLayout() - minilayout.setContentsMargins(0, 0, 0, 0) - minilayout.setSpacing(0) - miniwidget.setLayout(minilayout) - - self.title_label = ElideLabel(f"

Error

", parent=miniwidget) - self.title_label.setAlignment(Qt.AlignTop) - self.title_label.setObjectName("game_widget") - minilayout.addWidget(self.title_label, stretch=2) - - minilayout.setAlignment(Qt.AlignTop) - layout.addWidget(miniwidget) - - self.setLayout(layout) - - def set_game(self, app_name): - self.game = self.core.get_game(app_name, False) - if (not self.game) or self.game.is_dlc: - # Don't show for EOS Overlay or DLCs - self.game = None - self.setVisible(False) - return - self.setVisible(True) - self.title_label.setText(f"

{self.game.app_title}

") - self.image.hideProgress(True) - self.image.showProgress( - self.image_manager.get_pixmap(app_name, color=True), - self.image_manager.get_pixmap(app_name, color=False), - ) - - def set_status(self, s: int): - if not self.game: - # Don't show for EOS Overlay or DLCs - return - self.image.updateProgress(s) diff --git a/rare/components/tabs/games/game_widgets/list_game_widget.py b/rare/components/tabs/games/game_widgets/list_game_widget.py new file mode 100644 index 00000000..622078ab --- /dev/null +++ b/rare/components/tabs/games/game_widgets/list_game_widget.py @@ -0,0 +1,181 @@ +from logging import getLogger + +from PyQt5.QtCore import Qt, QEvent, QRect +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtGui import ( + QPalette, + QBrush, + QPaintEvent, + QPainter, + QLinearGradient, + QPixmap, QImage, QResizeEvent, +) + +from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame +from rare.utils.misc import get_size +from rare.widgets.image_widget import ImageWidget +from .game_widget import GameWidget +from .list_widget import ListWidget + +logger = getLogger("ListGameWidget") + + +class ListGameWidget(GameWidget): + signal = pyqtSignal(str) + update_game = pyqtSignal() + + def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None): + super(ListGameWidget, self).__init__(rgame, game_utils, parent) + self.setObjectName(f"{rgame.app_name}") + self.ui = ListWidget() + self.ui.setupUi(self) + + self.ui.title_label.setText(f"

{self.rgame.app_title}

") + + self.update_text() + + self.ui.install_btn.setVisible(not self.rgame.is_installed) + self.ui.install_btn.clicked.connect(self.install) + + self.ui.launch_btn.setText( + self.tr("Launch") if not self.rgame.is_origin else self.tr("Link/Play") + ) + self.ui.launch_btn.clicked.connect(self.launch) + self.ui.launch_btn.setVisible(self.rgame.is_installed) + + self.ui.developer_text.setText(self.rgame.developer) + # self.version_label.setVisible(self.is_installed) + if self.rgame.igame: + self.ui.version_text.setText(self.rgame.version) + + self.ui.size_text.setText(get_size(self.rgame.install_size) if self.rgame.install_size else "") + + # self.game_utils.cloud_save_finished.connect(self.sync_finished) + # self.game_utils.finished.connect(self.game_finished) + + # self.game_utils.game_launched.connect(self.game_started) + + self.ui.launch_btn.setEnabled(self.rgame.can_launch) + + def update_text(self, e=None): + if self.rgame.is_installed: + if self.rgame.has_update: + self.ui.status_label.setText(self.texts["default"]["update_available"]) + elif self.rgame.is_foreign: + self.ui.status_label.setText(self.texts["default"]["no_meta"]) + elif self.rgame.igame and self.rgame.needs_verification: + self.ui.status_label.setText(self.texts["static"]["needs_verification"]) + elif self.syncing_cloud_saves: + self.ui.status_label.setText(self.texts["default"]["syncing"]) + else: + self.ui.status_label.setText("") + self.ui.status_label.setVisible(False) + else: + self.ui.status_label.setVisible(False) + + def enterEvent(self, a0: QEvent = None) -> None: + status = self.enterEventText + self.ui.status_label.setText(status) + self.ui.status_label.setVisible(bool(status)) + + def leaveEvent(self, a0: QEvent = None) -> None: + status = self.leaveEventText + self.ui.status_label.setText(status) + self.ui.status_label.setVisible(bool(status)) + + def game_started(self, app_name): + if app_name == self.rgame.app_name: + self.game_running = True + self.update_text() + self.ui.launch_btn.setDisabled(True) + + def game_finished(self, app_name, error): + if app_name != self.rgame.app_name: + return + super().game_finished(app_name, error) + self.update_text(None) + self.ui.launch_btn.setDisabled(False) + + """ + Painting and progress overrides. + Let them live here until a better alternative is divised. + + The list widget and these painting functions can be + refactored to be used in downloads and/or dlcs + """ + + def event(self, e: QEvent) -> bool: + if e.type() == QEvent.LayoutRequest: + if self.progress_label.isVisible(): + width = int(self._pixmap.width() / self._pixmap.devicePixelRatioF()) + origin = self.width() - width + fill_rect = QRect(origin, 0, width, self.sizeHint().height()) + self.progress_label.setGeometry(fill_rect) + return ImageWidget.event(self, e) + + def resizeEvent(self, a0: QResizeEvent) -> None: + if self.progress_label.isVisible(): + width = int(self._pixmap.width() / self._pixmap.devicePixelRatioF()) + origin = self.width() - width + fill_rect = QRect(origin, 0, width, self.sizeHint().height()) + self.progress_label.setGeometry(fill_rect) + ImageWidget.resizeEvent(self, a0) + + def prepare_pixmap(self, pixmap: QPixmap) -> QPixmap: + device: QImage = QImage( + pixmap.size().width() * 3, + int(self.sizeHint().height() * pixmap.devicePixelRatioF()) + 1, + QImage.Format_ARGB32_Premultiplied + ) + painter = QPainter(device) + brush = QBrush(pixmap) + painter.fillRect(device.rect(), brush) + # the gradient could be cached and reused as it is expensive + gradient = QLinearGradient(0, 0, device.width(), 0) + gradient.setColorAt(0.15, Qt.transparent) + gradient.setColorAt(0.5, Qt.black) + gradient.setColorAt(0.85, Qt.transparent) + painter.setCompositionMode(QPainter.CompositionMode_DestinationIn) + painter.fillRect(device.rect(), gradient) + painter.end() + ret = QPixmap.fromImage(device) + ret.setDevicePixelRatio(pixmap.devicePixelRatioF()) + return ret + + def setPixmap(self, pixmap: QPixmap) -> None: + # lk: trade some possible delay and start-up time + # lk: for faster rendering. Gradients are expensive + # lk: so pre-generate the image + super(ListGameWidget, self).setPixmap(self.prepare_pixmap(pixmap)) + + def paint_image_cover(self, painter: QPainter, a0: QPaintEvent) -> None: + painter.setOpacity(self._opacity) + color = self.palette().color(QPalette.Background).darker(75) + painter.fillRect(self.rect(), color) + brush = QBrush(self._pixmap) + brush.setTransform(self._transform) + width = int(self._pixmap.width() / self._pixmap.devicePixelRatioF()) + origin = self.width() - width + painter.setBrushOrigin(origin, 0) + fill_rect = QRect(origin, 0, width, self.height()) + painter.fillRect(fill_rect, brush) + + def progressPixmap(self, color: QPixmap, gray: QPixmap, progress: int) -> QPixmap: + # lk: so about that +1 after the in convertion, casting to int rounds down + # lk: and that can create a weird line at the bottom, add 1 to round up. + device = QPixmap( + color.size().width(), + int(self.sizeHint().height() * color.devicePixelRatioF()) + 1, + ) + painter = QPainter(device) + painter.setRenderHint(QPainter.SmoothPixmapTransform, self._smooth_transform) + painter.setCompositionMode(QPainter.CompositionMode_Source) + prog_h = (device.height() * progress // 100) + brush = QBrush(gray) + painter.fillRect(device.rect().adjusted(0, 0, 0, -prog_h), brush) + brush.setTexture(color) + painter.fillRect(device.rect().adjusted(0, device.height() - prog_h, 0, 0), brush) + painter.end() + device.setDevicePixelRatio(color.devicePixelRatioF()) + return device diff --git a/rare/components/tabs/games/game_widgets/list_widget.py b/rare/components/tabs/games/game_widgets/list_widget.py new file mode 100644 index 00000000..a8191bd3 --- /dev/null +++ b/rare/components/tabs/games/game_widgets/list_widget.py @@ -0,0 +1,135 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import ( + QLabel, + QPushButton, + QSizePolicy, + QVBoxLayout, + QHBoxLayout, + QSpacerItem, + QWidget, +) + +from rare.utils.misc import icon +from rare.widgets.elide_label import ElideLabel + + +class ListWidget(object): + def setupUi(self, widget: QWidget): + self.title_label = QLabel(parent=widget) + self.title_label.setWordWrap(False) + + self.status_label = QLabel(parent=widget) + self.status_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + self.status_label.setStyleSheet( + "background-color: rgba(0,0,0,75%);" + "border: 1px solid black;" + "border-radius: 5px;" + ) + + self.install_btn = QPushButton(parent=widget) + self.install_btn.setIcon(icon("ri.install-fill")) + self.install_btn.setStyleSheet("text-align:left") + self.install_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.install_btn.setFixedWidth(120) + + self.launch_btn = QPushButton(parent=widget) + self.launch_btn.setIcon(icon("ei.play-alt")) + self.launch_btn.setStyleSheet("text-align:left") + self.launch_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.launch_btn.setFixedWidth(120) + + # self.info_btn = QPushButton(parent=widget) + # self.info_btn.setIcon(icon("ei.info-circle")) + # self.info_btn.setStyleSheet("text-align:left") + # self.info_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + # self.info_btn.setFixedWidth(120) + + font = QFont() + font.setBold(True) + # font.setWeight(75) + + # self.developer_label = QLabel(parent=widget) + # self.developer_label.setStyleSheet(f"color: #999;") + # self.developer_label.setFont(font) + self.developer_text = ElideLabel(parent=widget) + self.developer_text.setFixedWidth(120) + self.developer_text.setStyleSheet(f"color: #999;") + + # self.version_label = QLabel(parent=widget) + # self.version_label.setStyleSheet(f"color: #999;") + # self.version_label.setFont(font) + self.version_text = ElideLabel(parent=widget) + self.version_text.setFixedWidth(120) + self.version_text.setStyleSheet(f"color: #999;") + + # self.size_label = QLabel(parent=widget) + # self.size_label.setStyleSheet(f"color: #999;") + # self.size_label.setFont(font) + self.size_text = ElideLabel(parent=widget) + self.size_text.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + self.size_text.setFixedWidth(60) + self.size_text.setStyleSheet(f"color: #999;") + + # Create layouts + top_layout = QHBoxLayout() + top_layout.setAlignment(Qt.AlignLeft) + + bottom_layout = QHBoxLayout() + bottom_layout.setAlignment(Qt.AlignRight) + + layout = QVBoxLayout() + layout.setSpacing(0) + layout.setContentsMargins(3, 3, 3, 3) + + # Layout the widgets + # (from inner to outer) + top_layout.addWidget(self.title_label, stretch=1) + # top_layout.addWidget(self.status_label, stretch=0) + + bottom_layout.addWidget(self.developer_text, stretch=0, alignment=Qt.AlignLeft) + bottom_layout.addItem(QSpacerItem(20, 0, QSizePolicy.Fixed, QSizePolicy.Minimum)) + bottom_layout.addWidget(self.version_text, stretch=0, alignment=Qt.AlignLeft) + bottom_layout.addItem(QSpacerItem(20, 0, QSizePolicy.Fixed, QSizePolicy.Minimum)) + bottom_layout.addWidget(self.size_text, stretch=0, alignment=Qt.AlignLeft) + bottom_layout.addItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + bottom_layout.addWidget(self.status_label, stretch=0, alignment=Qt.AlignRight) + # bottom_layout.addWidget(self.info_btn, stretch=0, alignment=Qt.AlignRight) + bottom_layout.addWidget(self.install_btn, stretch=0, alignment=Qt.AlignRight) + bottom_layout.addWidget(self.launch_btn, stretch=0, alignment=Qt.AlignRight) + + layout.addLayout(top_layout) + layout.addLayout(bottom_layout) + + widget.setLayout(layout) + + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) + + # lk: for debug, DO NOT REMOVE + # self.image.setObjectName(f"{type(self).__name__}_image") + # self.title_label.setObjectName(f"{type(self).__name__}_title_label") + # self.status_label.setObjectName(f"{type(self).__name__}_status_label") + # self.status_label.setObjectName(f"{type(self).__name__}_progress") + # self.install_btn.setObjectName(f"{type(self).__name__}_install_btn") + # self.launch_btn.setObjectName(f"{type(self).__name__}_launch_btn") + # self.info_btn.setObjectName(f"{type(self).__name__}_info_btn") + # self.developer_label.setObjectName(f"{type(self).__name__}_developer_label") + # self.developer_text.setObjectName(f"{type(self).__name__}_developer_text") + # self.version_label.setObjectName(f"{type(self).__name__}_version_label") + # self.version_text.setObjectName(f"{type(self).__name__}_version_text") + # self.size_label.setObjectName(f"{type(self).__name__}_size_label") + # self.size_text.setObjectName(f"{type(self).__name__}_size_text") + # middle_layout.setObjectName(f"{type(self).__name__}_info_layout") + # form_layout.setObjectName(f"{type(self).__name__}_form_layout") + # right_layout.setObjectName(f"{type(self).__name__}_button_layout") + # right_layout.setObjectName(f"{type(self).__name__}_right_layout") + # layout.setObjectName(f"{type(self).__name__}_layout") + + self.translateUi(widget) + + def translateUi(self, widget: QWidget): + # self.info_btn.setText(widget.tr("Information")) + self.install_btn.setText(widget.tr("Install")) + # self.developer_label.setText(widget.tr("Developer")) + # self.version_label.setText(widget.tr("Version")) + # self.size_label.setText(widget.tr("Installed size")) diff --git a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py b/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py deleted file mode 100644 index c0c2e548..00000000 --- a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py +++ /dev/null @@ -1,67 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QVBoxLayout, QWidget, QHBoxLayout -from legendary.core import LegendaryCore -from legendary.models.game import Game - -from rare.components.tabs.games.game_widgets.base_uninstalled_widget import ( - BaseUninstalledWidget, -) -from rare.shared.image_manager import ImageSize -from rare.widgets.elide_label import ElideLabel - -logger = getLogger("Uninstalled") - - -class UninstalledIconWidget(BaseUninstalledWidget): - def __init__(self, game: Game, core: LegendaryCore, pixmap): - super(UninstalledIconWidget, self).__init__(game, core, pixmap) - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - self.setObjectName(type(self).__name__) - layout.addWidget(self.image) - - miniwidget = QWidget(self) - miniwidget.setFixedWidth(ImageSize.Display.size.width()) - minilayout = QHBoxLayout() - minilayout.setContentsMargins(0, 0, 0, 0) - minilayout.setSpacing(0) - miniwidget.setLayout(minilayout) - - self.title_label = ElideLabel(f"{game.app_title}", parent=miniwidget) - self.title_label.setAlignment(Qt.AlignTop) - self.title_label.setObjectName("game_widget") - minilayout.addWidget(self.title_label, stretch=2) - - minilayout.setAlignment(Qt.AlignTop) - layout.addWidget(miniwidget) - - self.info_label = ElideLabel(" ", parent=self) - self.info_label.setFixedWidth(ImageSize.Display.size.width()) - self.leaveEvent(None) - self.info_label.setObjectName("info_label") - layout.addWidget(self.info_label) - - self.setLayout(layout) - - def mousePressEvent(self, e) -> None: - # left button - if e.button() == 1 and not self.installing: - self.install() - - # right - elif e.button() == 2: - pass # self.showMenu(e) - - def enterEvent(self, e): - if not self.installing: - self.info_label.setText(self.tr("Game Info")) - else: - self.info_label.setText(self.tr("Installation running")) - - def leaveEvent(self, e): - if self.installing: - self.info_label.setText("Installation...") - else: - self.info_label.setText(" ") # invisible text, cheap way to always have vertical size in label diff --git a/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py b/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py deleted file mode 100644 index 69fb100e..00000000 --- a/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py +++ /dev/null @@ -1,36 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QPushButton - -from legendary.core import LegendaryCore -from rare.components.tabs.games.game_widgets.base_uninstalled_widget import ( - BaseUninstalledWidget, -) - -logger = getLogger("Game") - - -class UninstalledListWidget(BaseUninstalledWidget): - def __init__(self, core: LegendaryCore, game, pixmap): - super(UninstalledListWidget, self).__init__(game, core, pixmap) - self.setFrameStyle(self.StyledPanel) - layout = QHBoxLayout() - self.setLayout(layout) - layout.addWidget(self.image) - - self.child_layout = QVBoxLayout() - layout.addLayout(self.child_layout) - - self.title_label = QLabel(f"

{self.game.app_title}

") - self.app_name_label = QLabel(f"App Name: {self.game.app_name}") - self.install_button = QPushButton(self.tr("Install")) - self.install_button.setFixedWidth(120) - self.install_button.clicked.connect(self.install) - - self.child_layout.addWidget(self.title_label) - self.child_layout.addWidget(self.app_name_label) - self.child_layout.addWidget(self.install_button) - - layout.setAlignment(Qt.AlignLeft) - self.child_layout.setAlignment(Qt.AlignTop) diff --git a/rare/components/tray_icon.py b/rare/components/tray_icon.py index 6c049daf..4db7358c 100644 --- a/rare/components/tray_icon.py +++ b/rare/components/tray_icon.py @@ -66,7 +66,7 @@ class TrayIcon(QSystemTrayIcon): self.setContextMenu(self.menu) self.signals = GlobalSignalsSingleton() - self.signals.game_uninstalled.connect(self.remove_button) + self.signals.game.uninstalled.connect(self.remove_button) def remove_button(self, app_name: str): if action := next((i for i in self.game_actions if i.property("app_name") == app_name), None): diff --git a/rare/models/game.py b/rare/models/game.py index 6b297cc4..f17ddbab 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -71,17 +71,17 @@ class RareGame(QObject): progress: int = 0 active_thread: Optional[QRunnable] = None - def __init__(self, legendary_core: LegendaryCore, image_manager: ImageManager, game: Game): + def __init__(self, game: Game, legendary_core: LegendaryCore, image_manager: ImageManager): super(RareGame, self).__init__() self.signals = RareGame.Signals() self.core = legendary_core self.image_manager = image_manager - # Update names for Unreal Engine - if game.app_title == "Unreal Engine": - game.app_title += f" {game.app_name.split('_')[-1]}" self.game: Game = game + # Update names for Unreal Engine + if self.game.app_title == "Unreal Engine": + self.game.app_title += f" {self.game.app_name.split('_')[-1]}" # None if origin or not installed self.igame: Optional[InstalledGame] = self.core.get_installed_game(game.app_name) diff --git a/rare/models/signals.py b/rare/models/signals.py index 7ae32cdb..3524a89b 100644 --- a/rare/models/signals.py +++ b/rare/models/signals.py @@ -10,18 +10,34 @@ class GlobalSignals(QObject): set_main_tab_index = pyqtSignal(int) # tab index update_download_tab_text = pyqtSignal() - dl_progress = pyqtSignal(int) # 0-100 - # set visibility of installing widget in games tab - installation_started = pyqtSignal(str) # app_name - add_download = pyqtSignal(str) + class ProgressSignals(QObject): + # str: app_name + started = pyqtSignal(str) + # str: app_name, int: progress + value = pyqtSignal(str, int) + # str: app_name, bool: stopped + finished = pyqtSignal(str, bool) + progress = ProgressSignals() - install_game = pyqtSignal(InstallOptionsModel) - installation_finished = pyqtSignal(bool, str) + class GameSignals(QObject): + install = pyqtSignal(InstallOptionsModel) + # list of app_name + installed = pyqtSignal(list) + # str: app_name + uninstalled = pyqtSignal(str) + # str: app_name + verified = pyqtSignal(str) + game = GameSignals() + + class DownloadSignals(QObject): + # str: app_name + enqueue_game = pyqtSignal(str) + download = DownloadSignals() overlay_installation_finished = pyqtSignal() - update_gamelist = pyqtSignal(list) - game_uninstalled = pyqtSignal(str) # appname + # update_gamelist = pyqtSignal(list) + # game_uninstalled = pyqtSignal(str) set_discord_rpc = pyqtSignal(str) # app_name of running game rpc_settings_updated = pyqtSignal()