1
0
Fork 0
mirror of synced 2024-06-27 18:51:06 +12:00

Library: Introduce new tile design from refactor_backend

Currently broken but Rare starts

Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
This commit is contained in:
loathingKernel 2022-12-25 04:21:23 +02:00
parent ab858b87d3
commit 96b80bc423
23 changed files with 1084 additions and 1040 deletions

View file

@ -25,18 +25,18 @@ class TabWidget(QTabWidget):
self.setTabBar(MainTabBar(disabled_tab)) self.setTabBar(MainTabBar(disabled_tab))
# Generate Tabs # Generate Tabs
self.games_tab = GamesTab() self.games_tab = GamesTab(self)
self.addTab(self.games_tab, self.tr("Games")) self.addTab(self.games_tab, self.tr("Games"))
if not self.args.offline: if not self.args.offline:
# updates = self.games_tab.default_widget.game_list.updates # 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.addTab(
self.downloads_tab, self.downloads_tab,
"Downloads" "Downloads"
+ ( + (
" (" + str(len(self.games_tab.updates)) + ")" " (" + str(len(self.games_tab.game_updates)) + ")"
if len(self.games_tab.updates) != 0 if len(self.games_tab.game_updates) != 0
else "" else ""
), ),
) )

View file

@ -1,6 +1,6 @@
import datetime import datetime
from logging import getLogger 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.QtCore import QThread, pyqtSignal, QSettings, pyqtSlot
from PyQt5.QtWidgets import ( 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.dl_queue_widget import DlQueueWidget, DlWidget
from rare.components.tabs.downloads.download_thread import DownloadThread from rare.components.tabs.downloads.download_thread import DownloadThread
from rare.lgndr.models.downloading import UIUpdate from rare.lgndr.models.downloading import UIUpdate
from rare.models.game import RareGame
from rare.models.install import InstallOptionsModel, InstallQueueItemModel from rare.models.install import InstallOptionsModel, InstallQueueItemModel
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab 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_queue: List[InstallQueueItemModel] = []
dl_status = pyqtSignal(int) dl_status = pyqtSignal(int)
def __init__(self, updates: Set): def __init__(self, game_updates: Set[RareGame], parent=None):
super(DownloadsTab, self).__init__() super(DownloadsTab, self).__init__(parent=parent)
self.setupUi(self) self.setupUi(self)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()
self.active_game: Game = None
self.active_game: Optional[Game] = None
self.analysis = None self.analysis = None
self.kill_button.clicked.connect(self.stop_download) 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_text = QLabel(self.tr("No updates available"))
self.update_layout.addWidget(self.update_text) 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: for rgame in game_updates:
self.add_update(app_name) self.add_update(rgame)
self.queue_widget.item_removed.connect(self.queue_item_removed) self.queue_widget.item_removed.connect(self.queue_item_removed)
self.signals.install_game.connect(self.get_install_options) self.signals.game.install.connect(self.get_install_options)
self.signals.game_uninstalled.connect(self.queue_item_removed) self.signals.game.uninstalled.connect(self.queue_item_removed)
self.signals.game_uninstalled.connect(self.remove_update) 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) 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() self.reset_infos()
@ -77,17 +79,17 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
w.update_button.setDisabled(False) w.update_button.setDisabled(False)
w.update_with_settings.setDisabled(False) w.update_with_settings.setDisabled(False)
def add_update(self, app_name: str): def add_update(self, rgame: RareGame):
if old_widget := self.update_widgets.get(app_name, False): if old_widget := self.update_widgets.get(rgame.app_name, False):
old_widget.deleteLater() old_widget.deleteLater()
self.update_widgets.pop(app_name) self.update_widgets.pop(rgame.app_name)
widget = UpdateWidget(self.core, app_name, self) widget = UpdateWidget(self.core, rgame, self)
self.update_layout.addWidget(widget) 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) widget.update_signal.connect(self.get_install_options)
if QSettings().value("auto_update", False, bool): if QSettings().value("auto_update", False, bool):
self.get_install_options( 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) widget.update_button.setDisabled(True)
self.update_text.setVisible(False) self.update_text.setVisible(False)
@ -145,7 +147,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
self.analysis = queue_item.download.analysis self.analysis = queue_item.download.analysis
self.dl_name.setText(self.active_game.app_title) 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) @pyqtSlot(DownloadThread.ReturnStatus)
def status(self, result: DownloadThread.ReturnStatus): def status(self, result: DownloadThread.ReturnStatus):
@ -179,10 +181,10 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
self.remove_update(game.app_name) self.remove_update(game.app_name)
self.signals.send_notification.emit(game.app_title) 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.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() self.reset_infos()
@ -199,7 +201,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
if w := self.update_widgets.get(self.active_game.app_name): if w := self.update_widgets.get(self.active_game.app_name):
w.update_button.setDisabled(False) w.update_button.setDisabled(False)
w.update_with_settings.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 self.active_game = None
if self.dl_queue: if self.dl_queue:
self.start_installation(self.dl_queue[0]) self.start_installation(self.dl_queue[0])
@ -214,7 +216,8 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
self.downloaded.setText("n/a") self.downloaded.setText("n/a")
self.analysis = None 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( self.progress_bar.setValue(
100 * ui_update.total_downloaded // self.analysis.dl_size 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)}" 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.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 100 * ui_update.total_downloaded // self.analysis.dl_size
) )
@ -265,14 +269,13 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
class UpdateWidget(QWidget): class UpdateWidget(QWidget):
update_signal = pyqtSignal(InstallOptionsModel) 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) super(UpdateWidget, self).__init__(parent=parent)
self.core = core self.core = core
self.game: Game = core.get_game(app_name) self.rgame = rgame
self.igame: InstalledGame = self.core.get_installed_game(app_name)
layout = QVBoxLayout() layout = QVBoxLayout()
self.title = QLabel(self.igame.title) self.title = QLabel(self.rgame.app_title)
layout.addWidget(self.title) layout.addWidget(self.title)
self.update_button = QPushButton(self.tr("Update Game")) self.update_button = QPushButton(self.tr("Update Game"))
@ -284,7 +287,7 @@ class UpdateWidget(QWidget):
layout.addWidget( layout.addWidget(
QLabel( QLabel(
self.tr("Version: <b>{}</b> >> <b>{}</b>") self.tr("Version: <b>{}</b> >> <b>{}</b>")
.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_button.setDisabled(True)
self.update_with_settings.setDisabled(True) self.update_with_settings.setDisabled(True)
self.update_signal.emit( 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 ) # True if settings

View file

@ -36,7 +36,7 @@ class DownloadThread(QThread):
shortcuts: bool = False shortcuts: bool = False
ret_status = pyqtSignal(ReturnStatus) ret_status = pyqtSignal(ReturnStatus)
ui_update = pyqtSignal(UIUpdate) ui_update = pyqtSignal(str, UIUpdate)
def __init__(self, core: LegendaryCore, item: InstallQueueItemModel): def __init__(self, core: LegendaryCore, item: InstallQueueItemModel):
super(DownloadThread, self).__init__() super(DownloadThread, self).__init__()
@ -56,7 +56,8 @@ class DownloadThread(QThread):
time.sleep(1) time.sleep(1)
while self.item.download.dlm.is_alive(): while self.item.download.dlm.is_alive():
try: 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: except queue.Empty:
pass pass
if self.dlm_signals.update: if self.dlm_signals.update:

View file

@ -1,3 +1,4 @@
import time
from logging import getLogger from logging import getLogger
from typing import Tuple, Dict, Union, List, Set 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 PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
from legendary.models.game import InstalledGame, Game from legendary.models.game import InstalledGame, Game
from rare.models.game import RareGame
from rare.shared import ( from rare.shared import (
LegendaryCoreSingleton, LegendaryCoreSingleton,
GlobalSignalsSingleton, GlobalSignalsSingleton,
@ -17,13 +19,9 @@ from rare.widgets.sliding_stack import SlidingStackedWidget
from .cloud_save_utils import CloudSaveUtils from .cloud_save_utils import CloudSaveUtils
from .game_info import GameInfoTabs from .game_info import GameInfoTabs
from .game_utils import GameUtils from .game_utils import GameUtils
from .game_widgets.base_installed_widget import BaseInstalledWidget from .game_widgets import LibraryWidgetController
from .game_widgets.base_uninstalled_widget import BaseUninstalledWidget from .game_widgets.icon_game_widget import IconGameWidget
from .game_widgets.installed_icon_widget import InstalledIconWidget from .game_widgets.list_game_widget import ListGameWidget
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 .head_bar import GameListHeadBar from .head_bar import GameListHeadBar
from .integrations import IntegrationsTabs from .integrations import IntegrationsTabs
@ -40,17 +38,15 @@ class GamesTab(QStackedWidget):
self.image_manager = ImageManagerSingleton() self.image_manager = ImageManagerSingleton()
self.settings = QSettings() self.settings = QSettings()
self.widgets: Dict[str, Tuple[ self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {}
Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = {} self.game_updates: Set[RareGame] = set()
self.updates: Set = set()
self.active_filter: int = 0 self.active_filter: int = 0
self.uninstalled_games: List[Game] = []
self.game_list: List[Game] = self.api_results.game_list self.game_list: List[Game] = self.api_results.game_list
self.dlcs = self.api_results.dlcs self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs
self.bit32 = self.api_results.bit32_games self.bit32: List[str] = self.api_results.bit32_games
self.mac_games = self.api_results.mac_games self.mac_games: List[str] = self.api_results.mac_games
self.no_assets = self.api_results.no_asset_games self.no_assets: List[Game] = self.api_results.no_asset_games
self.game_utils = GameUtils(parent=self) 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.head_bar.goto_eos_ubisoft.connect(self.show_eos_ubisoft)
self.games.layout().addWidget(self.head_bar) 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.game_info_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games))
self.addWidget(self.game_info_tabs) 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.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentWidget(self.games))
self.integrations_tabs = IntegrationsTabs(self) self.integrations_tabs = IntegrationsTabs(self)
@ -96,8 +89,6 @@ class GamesTab(QStackedWidget):
else: else:
self.no_assets = [] self.no_assets = []
self.installed = self.core.get_installed_list()
self.view_stack = SlidingStackedWidget(self.games) self.view_stack = SlidingStackedWidget(self.games)
self.view_stack.setFrameStyle(QFrame.NoFrame) self.view_stack.setFrameStyle(QFrame.NoFrame)
self.icon_view_scroll = QScrollArea(self.view_stack) 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.setLayout(QVBoxLayout(self.list_view))
self.list_view.layout().setContentsMargins(3, 3, 9, 3) self.list_view.layout().setContentsMargins(3, 3, 9, 3)
self.list_view.layout().setAlignment(Qt.AlignTop) 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.icon_view_scroll.setWidget(self.icon_view)
self.list_view_scroll.setWidget(self.list_view) self.list_view_scroll.setWidget(self.list_view)
self.view_stack.addWidget(self.icon_view_scroll) self.view_stack.addWidget(self.icon_view_scroll)
self.view_stack.addWidget(self.list_view_scroll) self.view_stack.addWidget(self.list_view_scroll)
self.games.layout().addWidget(self.view_stack) 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): if not self.settings.value("icon_view", True, bool):
self.view_stack.setCurrentWidget(self.list_view_scroll) self.view_stack.setCurrentWidget(self.list_view_scroll)
self.head_bar.view.list() self.head_bar.view.list()
@ -145,7 +134,7 @@ class GamesTab(QStackedWidget):
) )
) )
self.head_bar.filterChanged.connect(self.filter_games) 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) self.head_bar.view.toggled.connect(self.toggle_view)
f = self.settings.value("filter", 0, int) f = self.settings.value("filter", 0, int)
@ -154,40 +143,16 @@ class GamesTab(QStackedWidget):
self.active_filter = self.head_bar.available_filters[f] self.active_filter = self.head_bar.available_filters[f]
# signals # signals
self.signals.dl_progress.connect(self.installing_widget.set_status) self.signals.game.installed.connect(self.update_count_games_label)
self.signals.installation_started.connect(self.installation_started) self.signals.game.uninstalled.connect(self.update_count_games_label)
self.signals.update_gamelist.connect(self.update_list) self.signals.game.uninstalled.connect(lambda x: self.setCurrentIndex(0))
self.signals.installation_finished.connect(
self.installation_finished
)
self.signals.game_uninstalled.connect(lambda name: self.update_list([name]))
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() self.setup_game_list()
print(f"Game list setup time: {time.time() - start_t}")
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()
@pyqtSlot() @pyqtSlot()
def show_import(self): def show_import(self):
@ -204,93 +169,55 @@ class GamesTab(QStackedWidget):
self.setCurrentWidget(self.integrations_tabs) self.setCurrentWidget(self.integrations_tabs)
self.integrations_tabs.show_eos_ubisoft() self.integrations_tabs.show_eos_ubisoft()
@pyqtSlot(str) @pyqtSlot(RareGame)
def show_game_info(self, app_name): def show_game_info(self, rgame):
self.game_info_tabs.update_game(app_name) self.game_info_tabs.update_game(rgame)
self.setCurrentWidget(self.game_info_tabs) self.setCurrentWidget(self.game_info_tabs)
@pyqtSlot() @pyqtSlot()
def update_count_games_label(self): def update_count_games_label(self):
self.head_bar.set_games_count(len(self.core.get_installed_list()), len(self.game_list)) 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): def setup_game_list(self):
self.update_count_games_label() self.update_count_games_label()
for game in self.game_list + self.no_assets:
# add installed games rgame = self.__create_game_with_dlcs(game)
for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title): icon_widget, list_widget = self.add_library_widget(rgame)
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)
if not icon_widget or not list_widget: 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 continue
self.icon_view.layout().addWidget(icon_widget) self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget) self.list_view.layout().addWidget(list_widget)
rgame.set_pixmap()
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)
self.filter_games(self.active_filter) self.filter_games(self.active_filter)
def add_installed_widget(self, app_name): def add_library_widget(self, rgame: RareGame):
pixmap = self.image_manager.get_pixmap(app_name)
try: try:
if pixmap.isNull(): icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils, self)
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)
except Exception as e: 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 return None, None
self.widgets[rgame.app_name] = (icon_widget, list_widget)
self.widgets[app_name] = (icon_widget, list_widget)
icon_widget.show_info.connect(self.show_game_info) icon_widget.show_info.connect(self.show_game_info)
list_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 return icon_widget, list_widget
@pyqtSlot(str)
@pyqtSlot(str, str)
def filter_games(self, filter_name="all", search_text: str = ""): def filter_games(self, filter_name="all", search_text: str = ""):
if not search_text and (t := self.head_bar.search_bar.text()): if not search_text and (t := self.head_bar.search_bar.text()):
search_text = t search_text = t
@ -300,177 +227,7 @@ class GamesTab(QStackedWidget):
if not filter_name and (t := self.active_filter): if not filter_name and (t := self.active_filter):
filter_name = t filter_name = t
def get_visibility(widget) -> Tuple[bool, float]: self.library_controller.filter_list(filter_name, search_text.lower())
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()
def toggle_view(self): def toggle_view(self):
self.settings.setValue("icon_view", not self.head_bar.view.isChecked()) self.settings.setValue("icon_view", not self.head_bar.view.isChecked())

View file

@ -1,11 +1,11 @@
from typing import Optional from typing import Optional
from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWidgets import QTreeView from PyQt5.QtWidgets import QTreeView
from legendary.models.game import Game
from rare.components.tabs.games.game_utils import GameUtils from rare.components.tabs.games.game_utils import GameUtils
from rare.models.game import RareGame
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
from rare.utils.extra_widgets import SideTabWidget from rare.utils.extra_widgets import SideTabWidget
from rare.utils.json_formatter import QJsonModel from rare.utils.json_formatter import QJsonModel
@ -15,7 +15,7 @@ from .game_settings import GameSettings
class GameInfoTabs(SideTabWidget): 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) super(GameInfoTabs, self).__init__(show_back=True, parent=parent)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()
@ -26,8 +26,7 @@ class GameInfoTabs(SideTabWidget):
self.settings = GameSettings(self) self.settings = GameSettings(self)
self.addTab(self.settings, self.tr("Settings")) self.addTab(self.settings, self.tr("Settings"))
self.dlc_list = dlcs self.dlc = GameDlc(game_utils, self)
self.dlc = GameDlc(self.dlc_list, game_utils, self)
self.addTab(self.dlc, self.tr("Downloadable Content")) self.addTab(self.dlc, self.tr("Downloadable Content"))
self.view = GameMetadataView() self.view = GameMetadataView()
@ -35,20 +34,16 @@ class GameInfoTabs(SideTabWidget):
self.tabBar().setCurrentIndex(1) self.tabBar().setCurrentIndex(1)
def update_game(self, app_name: str): def update_game(self, rgame: RareGame):
installed = bool(self.core.get_installed_game(app_name)) 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.dlc.update_dlcs(rgame)
self.settings.setEnabled(installed) self.dlc.setEnabled(bool(rgame.owned_dlcs))
self.dlc.setEnabled( self.view.update_game(rgame)
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.setCurrentIndex(1) self.setCurrentIndex(1)
@ -64,14 +59,14 @@ class GameMetadataView(QTreeView):
self.setWordWrap(True) self.setWordWrap(True)
self.model = QJsonModel() self.model = QJsonModel()
self.setModel(self.model) self.setModel(self.model)
self.game: Optional[Game] = None self.rgame: Optional[RareGame] = None
def update_game(self, game: Game): def update_game(self, rgame: RareGame):
self.game = game self.rgame = rgame
self.title.setTitle(self.game.app_title) self.title.setTitle(self.rgame.app_title)
self.model.clear() self.model.clear()
try: try:
self.model.load(game.__dict__) self.model.load(rgame.game.__dict__)
except: except Exception as e:
pass pass
self.resizeColumnToContents(0) self.resizeColumnToContents(0)

View file

@ -2,11 +2,11 @@ from typing import Optional
from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox
from legendary.models.game import Game
from rare.components.tabs.games.game_utils import GameUtils from rare.components.tabs.games.game_utils import GameUtils
from rare.models.game import RareGame
from rare.models.install import InstallOptionsModel 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.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 import Ui_GameDlc
from rare.ui.components.tabs.games.game_info.game_dlc_widget import Ui_GameDlcWidget 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): class GameDlc(QWidget):
install_dlc = pyqtSignal(str, bool) 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) super(GameDlc, self).__init__(parent=parent)
self.ui = Ui_GameDlc() self.ui = Ui_GameDlc()
self.ui.setupUi(self) self.ui.setupUi(self)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()
self.game: Optional[Game] = None self.rgame: Optional[RareGame] = None
self.game_utils = game_utils self.game_utils = game_utils
self.ui.available_dlc_scroll.setFrameStyle(QFrame.NoFrame) self.ui.available_dlc_scroll.setFrameStyle(QFrame.NoFrame)
self.ui.installed_dlc_scroll.setFrameStyle(QFrame.NoFrame) self.ui.installed_dlc_scroll.setFrameStyle(QFrame.NoFrame)
self.dlcs = dlcs
self.installed_dlc_widgets = list() self.installed_dlc_widgets = list()
self.available_dlc_widgets = list() self.available_dlc_widgets = list()
def update_dlcs(self, app_name): def update_dlcs(self, rgame: RareGame):
self.game = self.core.get_game(app_name) self.rgame = rgame
dlcs = self.dlcs[self.game.catalog_item_id] self.title.setTitle(self.rgame.app_title)
self.title.setTitle(self.game.app_title)
if self.installed_dlc_widgets: if self.installed_dlc_widgets:
for dlc_widget in self.installed_dlc_widgets: for dlc_widget in self.installed_dlc_widgets:
@ -49,8 +47,8 @@ class GameDlc(QWidget):
dlc_widget.deleteLater() dlc_widget.deleteLater()
self.available_dlc_widgets.clear() self.available_dlc_widgets.clear()
for dlc in sorted(dlcs, key=lambda x: x.app_title): for dlc in sorted(self.rgame.owned_dlcs, key=lambda x: x.app_title):
if self.core.is_installed(dlc.app_name): if dlc.is_installed:
dlc_widget = GameDlcWidget(dlc, True) dlc_widget = GameDlcWidget(dlc, True)
self.ui.installed_dlc_contents_layout.addWidget(dlc_widget) self.ui.installed_dlc_contents_layout.addWidget(dlc_widget)
dlc_widget.uninstall.connect(self.uninstall) 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_label.setVisible(not self.available_dlc_widgets)
self.ui.available_dlc_scroll.setVisible(bool(self.available_dlc_widgets)) self.ui.available_dlc_scroll.setVisible(bool(self.available_dlc_widgets))
@pyqtSlot(str) @pyqtSlot(RareGame)
def uninstall(self, app_name): def uninstall(self, rgame: RareGame):
if self.game_utils.uninstall_game(app_name): if self.game_utils.uninstall_game(rgame.app_name):
self.update_dlcs(self.game.app_name) self.update_dlcs(self.rgame)
def install(self, app_name): 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( QMessageBox.warning(
self, self,
"Error", "Error",
self.tr("Base Game is not installed. Please install {} first").format( self.tr("Base Game is not installed. Please install {} first").format(self.rgame.app_title),
self.game.app_title
),
) )
return return
self.signals.install_game.emit( self.signals.game.install.emit(InstallOptionsModel(app_name=app_name, update=True))
InstallOptionsModel(app_name=app_name, update=True)
)
class GameDlcWidget(QFrame): class GameDlcWidget(QFrame):
install = pyqtSignal(str) # Appname install = pyqtSignal(RareGame)
uninstall = pyqtSignal(str) 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) super(GameDlcWidget, self).__init__(parent=parent)
self.image_manager = ImageManagerSingleton()
self.ui = Ui_GameDlcWidget() self.ui = Ui_GameDlcWidget()
self.ui.setupUi(self) self.ui.setupUi(self)
self.dlc = dlc self.dlc = dlc
@ -105,10 +98,10 @@ class GameDlcWidget(QFrame):
self.ui.dlc_layout.insertWidget(0, self.image) self.ui.dlc_layout.insertWidget(0, self.image)
self.ui.dlc_name.setText(dlc.app_title) 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.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: if installed:
self.ui.action_button.setProperty("uninstall", 1) self.ui.action_button.setProperty("uninstall", 1)
@ -122,9 +115,9 @@ class GameDlcWidget(QFrame):
def uninstall_dlc(self): def uninstall_dlc(self):
self.ui.action_button.setDisabled(True) self.ui.action_button.setDisabled(True)
self.ui.action_button.setText(self.tr("Uninstalling")) self.ui.action_button.setText(self.tr("Uninstalling"))
self.uninstall.emit(self.dlc.app_name) self.uninstall.emit(self.dlc)
def install_game(self): def install_game(self):
self.ui.action_button.setDisabled(True) self.ui.action_button.setDisabled(True)
self.ui.action_button.setText(self.tr("Installing")) self.ui.action_button.setText(self.tr("Installing"))
self.install.emit(self.dlc.app_name) self.install.emit(self.dlc)

View file

@ -23,6 +23,7 @@ from PyQt5.QtWidgets import (
) )
from legendary.models.game import Game, InstalledGame from legendary.models.game import Game, InstalledGame
from rare.models.game import RareGame
from rare.models.install import InstallOptionsModel from rare.models.install import InstallOptionsModel
from rare.shared import ( from rare.shared import (
LegendaryCoreSingleton, LegendaryCoreSingleton,
@ -313,12 +314,13 @@ class GameInfo(QWidget, Ui_GameInfo):
def show_menu_after_browse(self): def show_menu_after_browse(self):
self.move_button.showMenu() self.move_button.showMenu()
def update_game(self, app_name: str): def update_game(self, rgame: RareGame):
self.game = self.core.get_game(app_name) # FIXME: Use RareGame for the rest of the code
self.igame = self.core.get_installed_game(self.game.app_name) self.game = rgame.game
self.igame = rgame.igame
self.title.setTitle(self.game.app_title) 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) self.app_name.setText(self.game.app_name)
if self.igame: if self.igame:
@ -361,7 +363,7 @@ class GameInfo(QWidget, Ui_GameInfo):
self.game_actions_stack.setCurrentIndex(0) self.game_actions_stack.setCurrentIndex(0)
try: try:
is_ue = self.core.get_asset(app_name).namespace == "ue" is_ue = self.core.get_asset(rgame.app_name).namespace == "ue"
except ValueError: except ValueError:
is_ue = False is_ue = False
grade_visible = not is_ue and platform.system() != "Windows" grade_visible = not is_ue and platform.system() != "Windows"
@ -403,5 +405,5 @@ class GameInfo(QWidget, Ui_GameInfo):
self.move_button.setEnabled(False) self.move_button.setEnabled(False)
self.verify_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)

View file

@ -8,6 +8,7 @@ from legendary.models.game import Game, InstalledGame
from rare.components.tabs.settings import DefaultGameSettings from rare.components.tabs.settings import DefaultGameSettings
from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings 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.shared.workers.wine_resolver import WineResolver
from rare.utils import config_helper from rare.utils import config_helper
from rare.utils.extra_widgets import PathEdit 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.remove_option(self.game.app_name, option)
config_helper.save_config() config_helper.save_config()
def load_settings(self, app_name): def load_settings(self, rgame: RareGame):
self.change = False self.change = False
# FIXME: Use RareGame for the rest of the code
app_name = rgame.app_name
super(GameSettings, self).load_settings(app_name) super(GameSettings, self).load_settings(app_name)
self.game = self.core.get_game(app_name) self.game = rgame.game
self.igame = self.core.get_installed_game(self.game.app_name) self.igame = rgame.igame
if self.igame: if self.igame:
if self.igame.can_run_offline: if self.igame.can_run_offline:
offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset") offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset")

View file

@ -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, "")

View file

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

View file

@ -1,40 +1,124 @@
import os import os
import platform import platform
from logging import getLogger from logging import getLogger
from typing import Optional
from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray from PyQt5.QtCore import pyqtSignal, QSettings, QStandardPaths, Qt
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QFrame, QMessageBox, QAction from PyQt5.QtWidgets import QMessageBox, QAction
from legendary.models.game import Game, InstalledGame
from rare.components.tabs.games.game_utils import GameUtils from rare.components.tabs.games.game_utils import GameUtils
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton from rare.models.game import RareGame
from rare.shared.image_manager import ImageSize from rare.shared import (
LegendaryCoreSingleton,
GlobalSignalsSingleton,
ArgumentsSingleton,
ImageManagerSingleton,
)
from rare.utils.misc import create_desktop_link 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): class GameWidget(LibraryWidget):
launch_signal = pyqtSignal(str, QProcess, list) show_info = pyqtSignal(RareGame)
show_info = pyqtSignal(str)
finish_signal = pyqtSignal(str, int)
proc: QProcess()
def __init__(self, app_name, pixmap: QPixmap, game_utils: GameUtils): def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
super(BaseInstalledWidget, self).__init__() super(GameWidget, self).__init__(parent=parent)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton() self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton() self.args = ArgumentsSingleton()
self.image_manager = ImageManagerSingleton() self.image_manager = ImageManagerSingleton()
self.game_utils = game_utils 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.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 = { self.texts = {
"needs_verification": self.tr("Please verify game before playing"), "static": {
"needs_verification": self.tr("Please verify game before playing"),
},
"hover": { "hover": {
"update_available": self.tr("Start game without version check"), "update_available": self.tr("Start without version check"),
"launch": self.tr("Launch Game"), "launch": self.tr("Launch Game"),
"launch_origin": self.tr("Launch/Link"), "launch_origin": self.tr("Launch/Link"),
"running": self.tr("Game running"), "running": self.tr("Game running"),
@ -49,101 +133,60 @@ class BaseInstalledWidget(QFrame):
}, },
} }
self.game = self.core.get_game(app_name) @property
self.igame = self.core.get_installed_game(app_name) # None if origin def enterEventText(self) -> str:
if self.rgame.is_installed:
if self.game.app_title == "Unreal Engine": if self.game_running:
self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}" return self.texts["hover"]["running"]
elif (not self.rgame.is_origin) and self.rgame.needs_verification:
self.is_only_offline = False return self.texts["static"]["needs_verification"]
elif self.rgame.is_foreign:
try: return self.texts["hover"]["launch_offline"]
self.core.get_asset(app_name, platform=self.igame.platform).build_version elif self.rgame.has_update:
except ValueError: return self.texts["hover"]["update_available"]
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
else: else:
if remote_version != self.igame.version: return self.tr("Game Info")
self.update_available = True # return self.texts["hover"]["launch" if self.igame else "launch_origin"]
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"))
else: else:
self.create_desktop = QAction(self.tr("Create Desktop link")) if not self.installing:
if self.igame: return self.tr("Game Info")
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"))
else: else:
self.create_start_menu = QAction(self.tr("Create start menu link")) return self.tr("Installation running")
if self.igame:
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) @property
reload_image.triggered.connect(self.reload_image) def leaveEventText(self) -> str:
self.addAction(reload_image) 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: def mousePressEvent(self, e: QMouseEvent) -> None:
uninstall = QAction(self.tr("Uninstall"), self) # left button
self.addAction(uninstall) if e.button() == 1:
uninstall.triggered.connect( self.show_info.emit(self.rgame)
lambda: self.signals.update_gamelist.emit([self.game.app_name]) # right
if self.game_utils.uninstall_game(self.game.app_name) elif e.button() == 2:
else None pass # self.showMenu(e)
)
def reload_image(self): def reload_image(self) -> None:
self.image_manager.download_image_blocking(self.game, force=True) self.rgame.refresh_pixmap()
pm = self.image_manager.get_pixmap(self.game.app_name, color=True)
self.image.setPixmap(pm) def install(self):
self.show_info.emit(self.rgame)
def create_desktop_link(self, type_of_link): def create_desktop_link(self, type_of_link):
if type_of_link == "desktop": if type_of_link == "desktop":
@ -154,9 +197,9 @@ class BaseInstalledWidget(QFrame):
return return
if platform.system() == "Windows": 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": 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: else:
QMessageBox.warning( QMessageBox.warning(
self, self,
@ -167,7 +210,7 @@ class BaseInstalledWidget(QFrame):
if not os.path.exists(shortcut_path): if not os.path.exists(shortcut_path):
try: 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 return
except PermissionError: except PermissionError:
QMessageBox.warning( QMessageBox.warning(
@ -189,16 +232,16 @@ class BaseInstalledWidget(QFrame):
def launch(self, offline=False, skip_version_check=False): def launch(self, offline=False, skip_version_check=False):
if self.game_running: if self.game_running:
return return
offline = offline or self.is_only_offline offline = offline or self.rgame.is_foreign
if self.is_only_offline and not self.igame.can_run_offline: if self.rgame.is_foreign and not self.rgame.can_run_offline:
QMessageBox.warning(self, "Warning", QMessageBox.warning(self, "Warning",
self.tr("This game is probably not in your library and it cannot be launched offline")) self.tr("This game is probably not in your library and it cannot be launched offline"))
return 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.syncing_cloud_saves = True
self.game_utils.prepare_launch( 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): def sync_finished(self, app_name):
@ -207,7 +250,7 @@ class BaseInstalledWidget(QFrame):
def sync_game(self): def sync_game(self):
try: try:
sync = self.game_utils.cloud_save_utils.sync_before_launch_game( sync = self.game_utils.cloud_save_utils.sync_before_launch_game(
self.game.app_name, True self.rgame.app_name, True
) )
except Exception: except Exception:
sync = False sync = False
@ -218,37 +261,3 @@ class BaseInstalledWidget(QFrame):
if error: if error:
QMessageBox.warning(self, "Error", error) QMessageBox.warning(self, "Error", error)
self.game_running = False 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

View file

@ -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"<h4>{self.rgame.app_title}</h4>")
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)

View file

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

View file

@ -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"<b>{self.game.app_title}</b>", 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)

View file

@ -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"<h1>{self.game.app_title}</h1>")
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)

View file

@ -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"<h4>Error</h4>", 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"<h4>{self.game.app_title}</h4>")
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)

View file

@ -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"<h3>{self.rgame.app_title}</h3>")
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

View file

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

View file

@ -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"<b>{game.app_title}</b>", 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

View file

@ -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"<h2>{self.game.app_title}</h2>")
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)

View file

@ -66,7 +66,7 @@ class TrayIcon(QSystemTrayIcon):
self.setContextMenu(self.menu) self.setContextMenu(self.menu)
self.signals = GlobalSignalsSingleton() 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): 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): if action := next((i for i in self.game_actions if i.property("app_name") == app_name), None):

View file

@ -71,17 +71,17 @@ class RareGame(QObject):
progress: int = 0 progress: int = 0
active_thread: Optional[QRunnable] = None 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__() super(RareGame, self).__init__()
self.signals = RareGame.Signals() self.signals = RareGame.Signals()
self.core = legendary_core self.core = legendary_core
self.image_manager = image_manager 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 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 # None if origin or not installed
self.igame: Optional[InstalledGame] = self.core.get_installed_game(game.app_name) self.igame: Optional[InstalledGame] = self.core.get_installed_game(game.app_name)

View file

@ -10,18 +10,34 @@ class GlobalSignals(QObject):
set_main_tab_index = pyqtSignal(int) # tab index set_main_tab_index = pyqtSignal(int) # tab index
update_download_tab_text = pyqtSignal() update_download_tab_text = pyqtSignal()
dl_progress = pyqtSignal(int) # 0-100 class ProgressSignals(QObject):
# set visibility of installing widget in games tab # str: app_name
installation_started = pyqtSignal(str) # app_name started = pyqtSignal(str)
add_download = 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) class GameSignals(QObject):
installation_finished = pyqtSignal(bool, str) 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() overlay_installation_finished = pyqtSignal()
update_gamelist = pyqtSignal(list) # update_gamelist = pyqtSignal(list)
game_uninstalled = pyqtSignal(str) # appname # game_uninstalled = pyqtSignal(str)
set_discord_rpc = pyqtSignal(str) # app_name of running game set_discord_rpc = pyqtSignal(str) # app_name of running game
rpc_settings_updated = pyqtSignal() rpc_settings_updated = pyqtSignal()