1
0
Fork 0
mirror of synced 2024-06-21 12:00:25 +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))
# Generate Tabs
self.games_tab = GamesTab()
self.games_tab = GamesTab(self)
self.addTab(self.games_tab, self.tr("Games"))
if not self.args.offline:
# updates = self.games_tab.default_widget.game_list.updates
self.downloads_tab = DownloadsTab(self.games_tab.updates)
self.downloads_tab = DownloadsTab(self.games_tab.game_updates, self)
self.addTab(
self.downloads_tab,
"Downloads"
+ (
" (" + str(len(self.games_tab.updates)) + ")"
if len(self.games_tab.updates) != 0
" (" + str(len(self.games_tab.game_updates)) + ")"
if len(self.games_tab.game_updates) != 0
else ""
),
)

View file

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

View file

@ -36,7 +36,7 @@ class DownloadThread(QThread):
shortcuts: bool = False
ret_status = pyqtSignal(ReturnStatus)
ui_update = pyqtSignal(UIUpdate)
ui_update = pyqtSignal(str, UIUpdate)
def __init__(self, core: LegendaryCore, item: InstallQueueItemModel):
super(DownloadThread, self).__init__()
@ -56,7 +56,8 @@ class DownloadThread(QThread):
time.sleep(1)
while self.item.download.dlm.is_alive():
try:
self.ui_update.emit(self.item.download.dlm.status_queue.get(timeout=1.0))
self.ui_update.emit(self.item.download.game.app_name,
self.item.download.dlm.status_queue.get(timeout=1.0))
except queue.Empty:
pass
if self.dlm_signals.update:

View file

@ -1,3 +1,4 @@
import time
from logging import getLogger
from typing import Tuple, Dict, Union, List, Set
@ -5,6 +6,7 @@ from PyQt5.QtCore import QSettings, Qt, pyqtSlot
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
from legendary.models.game import InstalledGame, Game
from rare.models.game import RareGame
from rare.shared import (
LegendaryCoreSingleton,
GlobalSignalsSingleton,
@ -17,13 +19,9 @@ from rare.widgets.sliding_stack import SlidingStackedWidget
from .cloud_save_utils import CloudSaveUtils
from .game_info import GameInfoTabs
from .game_utils import GameUtils
from .game_widgets.base_installed_widget import BaseInstalledWidget
from .game_widgets.base_uninstalled_widget import BaseUninstalledWidget
from .game_widgets.installed_icon_widget import InstalledIconWidget
from .game_widgets.installed_list_widget import InstalledListWidget
from .game_widgets.installing_game_widget import InstallingGameWidget
from .game_widgets.uninstalled_icon_widget import UninstalledIconWidget
from .game_widgets.uninstalled_list_widget import UninstalledListWidget
from .game_widgets import LibraryWidgetController
from .game_widgets.icon_game_widget import IconGameWidget
from .game_widgets.list_game_widget import ListGameWidget
from .head_bar import GameListHeadBar
from .integrations import IntegrationsTabs
@ -40,17 +38,15 @@ class GamesTab(QStackedWidget):
self.image_manager = ImageManagerSingleton()
self.settings = QSettings()
self.widgets: Dict[str, Tuple[
Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = {}
self.updates: Set = set()
self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {}
self.game_updates: Set[RareGame] = set()
self.active_filter: int = 0
self.uninstalled_games: List[Game] = []
self.game_list: List[Game] = self.api_results.game_list
self.dlcs = self.api_results.dlcs
self.bit32 = self.api_results.bit32_games
self.mac_games = self.api_results.mac_games
self.no_assets = self.api_results.no_asset_games
self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs
self.bit32: List[str] = self.api_results.bit32_games
self.mac_games: List[str] = self.api_results.mac_games
self.no_assets: List[Game] = self.api_results.no_asset_games
self.game_utils = GameUtils(parent=self)
@ -64,13 +60,10 @@ class GamesTab(QStackedWidget):
self.head_bar.goto_eos_ubisoft.connect(self.show_eos_ubisoft)
self.games.layout().addWidget(self.head_bar)
self.game_info_tabs = GameInfoTabs(self.dlcs, self.game_utils, self)
self.game_info_tabs = GameInfoTabs(self.game_utils, self)
self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games))
self.addWidget(self.game_info_tabs)
self.game_info_tabs.info.verification_finished.connect(
self.verification_finished
)
self.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentWidget(self.games))
self.integrations_tabs = IntegrationsTabs(self)
@ -96,8 +89,6 @@ class GamesTab(QStackedWidget):
else:
self.no_assets = []
self.installed = self.core.get_installed_list()
self.view_stack = SlidingStackedWidget(self.games)
self.view_stack.setFrameStyle(QFrame.NoFrame)
self.icon_view_scroll = QScrollArea(self.view_stack)
@ -116,17 +107,15 @@ class GamesTab(QStackedWidget):
self.list_view.setLayout(QVBoxLayout(self.list_view))
self.list_view.layout().setContentsMargins(3, 3, 9, 3)
self.list_view.layout().setAlignment(Qt.AlignTop)
self.library_controller = LibraryWidgetController(
self.icon_view, self.list_view, self
)
self.icon_view_scroll.setWidget(self.icon_view)
self.list_view_scroll.setWidget(self.list_view)
self.view_stack.addWidget(self.icon_view_scroll)
self.view_stack.addWidget(self.list_view_scroll)
self.games.layout().addWidget(self.view_stack)
# add installing game widget for icon view: List view not supported
self.installing_widget = InstallingGameWidget()
self.icon_view.layout().addWidget(self.installing_widget)
self.installing_widget.setVisible(False)
if not self.settings.value("icon_view", True, bool):
self.view_stack.setCurrentWidget(self.list_view_scroll)
self.head_bar.view.list()
@ -145,7 +134,7 @@ class GamesTab(QStackedWidget):
)
)
self.head_bar.filterChanged.connect(self.filter_games)
self.head_bar.refresh_list.clicked.connect(self.update_list)
self.head_bar.refresh_list.clicked.connect(self.library_controller.update_list)
self.head_bar.view.toggled.connect(self.toggle_view)
f = self.settings.value("filter", 0, int)
@ -154,40 +143,16 @@ class GamesTab(QStackedWidget):
self.active_filter = self.head_bar.available_filters[f]
# signals
self.signals.dl_progress.connect(self.installing_widget.set_status)
self.signals.installation_started.connect(self.installation_started)
self.signals.update_gamelist.connect(self.update_list)
self.signals.installation_finished.connect(
self.installation_finished
)
self.signals.game_uninstalled.connect(lambda name: self.update_list([name]))
self.signals.game.installed.connect(self.update_count_games_label)
self.signals.game.uninstalled.connect(self.update_count_games_label)
self.signals.game.uninstalled.connect(lambda x: self.setCurrentIndex(0))
self.game_utils.update_list.connect(self.update_list)
# self.signals.update_gamelist.connect(self.library_controller.update_list)
# self.game_utils.update_list.connect(self.library_controller.update_list)
start_t = time.time()
self.setup_game_list()
def installation_finished(self, app_name: str):
self.installing_widget.setVisible(False)
self.installing_widget.set_game("")
self.filter_games("")
def installation_started(self, app_name: str):
self.installing_widget.set_game(app_name)
i_widget, l_widget = self.widgets.get(app_name, (None, None))
if not i_widget or not l_widget:
return
i_widget.setVisible(False)
l_widget.setVisible(False)
def verification_finished(self, igame: InstalledGame):
# only if igame needs verification
i_widget, l_widget = self.widgets[igame.app_name]
i_widget.igame = igame
l_widget.igame = igame
i_widget.leaveEvent(None)
l_widget.update_text()
print(f"Game list setup time: {time.time() - start_t}")
@pyqtSlot()
def show_import(self):
@ -204,93 +169,55 @@ class GamesTab(QStackedWidget):
self.setCurrentWidget(self.integrations_tabs)
self.integrations_tabs.show_eos_ubisoft()
@pyqtSlot(str)
def show_game_info(self, app_name):
self.game_info_tabs.update_game(app_name)
@pyqtSlot(RareGame)
def show_game_info(self, rgame):
self.game_info_tabs.update_game(rgame)
self.setCurrentWidget(self.game_info_tabs)
@pyqtSlot()
def update_count_games_label(self):
self.head_bar.set_games_count(len(self.core.get_installed_list()), len(self.game_list))
# FIXME: Remove this when RareCore is in place
def __create_game_with_dlcs(self, game: Game) -> RareGame:
rgame = RareGame(game, self.core, self.image_manager)
if rgame.has_update:
self.game_updates.add(rgame)
if game_dlcs := self.dlcs[rgame.game.catalog_item_id]:
for dlc in game_dlcs:
rdlc = RareGame(dlc, self.core, self.image_manager)
if rdlc.has_update:
self.game_updates.add(rdlc)
rdlc.set_pixmap()
rgame.owned_dlcs.append(rdlc)
return rgame
def setup_game_list(self):
self.update_count_games_label()
# add installed games
for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title):
icon_widget, list_widget = self.add_installed_widget(igame.app_name)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
for game in self.no_assets:
if not game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin":
icon_widget, list_widget = self.add_uninstalled_widget(game)
else:
icon_widget, list_widget = self.add_installed_widget(game.app_name)
for game in self.game_list + self.no_assets:
rgame = self.__create_game_with_dlcs(game)
icon_widget, list_widget = self.add_library_widget(rgame)
if not icon_widget or not list_widget:
logger.warning(f"Ignoring {game.app_name} in game list")
logger.warning(f"Excluding {rgame.app_name} from the game list")
continue
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
for game in sorted(self.game_list, key=lambda x: x.app_title):
if not self.core.is_installed(game.app_name):
self.uninstalled_games.append(game)
icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
rgame.set_pixmap()
self.filter_games(self.active_filter)
def add_installed_widget(self, app_name):
pixmap = self.image_manager.get_pixmap(app_name)
def add_library_widget(self, rgame: RareGame):
try:
if pixmap.isNull():
logger.info(f"{app_name} has a corrupt image.")
if app_name in self.no_asset_names and self.core.get_asset(app_name).namespace != "ue":
self.image_manager.download_image_blocking(self.core.get_game(app_name), force=True)
pixmap = self.image_manager.get_pixmap(app_name)
elif self.ue_name:
pixmap = self.image_manager.get_pixmap(self.ue_name)
icon_widget = InstalledIconWidget(app_name, pixmap, self.game_utils)
list_widget = InstalledListWidget(app_name, pixmap, self.game_utils)
icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils, self)
except Exception as e:
logger.error(f"{app_name} is broken. Don't add it to game list: {e}")
logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}")
return None, None
self.widgets[app_name] = (icon_widget, list_widget)
self.widgets[rgame.app_name] = (icon_widget, list_widget)
icon_widget.show_info.connect(self.show_game_info)
list_widget.show_info.connect(self.show_game_info)
if icon_widget.update_available:
self.updates.add(app_name)
return icon_widget, list_widget
def add_uninstalled_widget(self, game):
pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
try:
if pixmap.isNull():
if self.core.get_asset(game.app_name).namespace != "ue":
logger.warning(f"{game.app_title} has a corrupt image. Reloading...")
self.image_manager.download_image_blocking(game, force=True)
pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
elif self.ue_name:
pixmap = self.image_manager.get_pixmap(self.ue_name, color=False)
icon_widget = UninstalledIconWidget(game, self.core, pixmap)
list_widget = UninstalledListWidget(self.core, game, pixmap)
except Exception as e:
logger.error(f"{game.app_name} is broken. Don't add it to game list: {e}")
return None, None
icon_widget.show_info.connect(self.show_game_info)
list_widget.show_info.connect(self.show_game_info)
self.widgets[game.app_name] = (icon_widget, list_widget)
return icon_widget, list_widget
@pyqtSlot(str)
@pyqtSlot(str, str)
def filter_games(self, filter_name="all", search_text: str = ""):
if not search_text and (t := self.head_bar.search_bar.text()):
search_text = t
@ -300,177 +227,7 @@ class GamesTab(QStackedWidget):
if not filter_name and (t := self.active_filter):
filter_name = t
def get_visibility(widget) -> Tuple[bool, float]:
app_name = widget.game.app_name
if not isinstance(widget,
InstallingGameWidget) and self.installing_widget.game and self.installing_widget.game.app_name == app_name:
visible = False
elif filter_name == "installed":
visible = self.core.is_installed(app_name)
elif filter_name == "offline":
if self.core.is_installed(app_name) and not isinstance(widget, InstallingGameWidget):
visible = widget.igame.can_run_offline
else:
visible = False
elif filter_name == "32bit" and self.bit32:
visible = app_name in self.bit32
elif filter_name == "mac" and self.mac_games:
visible = app_name in self.mac_games
elif filter_name == "installable":
visible = app_name not in self.no_asset_names
elif filter_name == "include_ue":
visible = True
elif filter_name == "all":
# All visible except ue
try:
visible = self.core.get_asset(app_name, update=False).namespace != "ue"
except ValueError:
visible = True
else:
visible = True
if (
search_text.lower() not in widget.game.app_name.lower()
and search_text.lower() not in widget.game.app_title.lower()
):
opacity = 0.25
else:
opacity = 1.0
return visible, opacity
for t in self.widgets.values():
visible, opacity = get_visibility(t[0])
for w in t:
w.setVisible(visible)
w.image.setOpacity(opacity)
self.sort_list(search_text)
if self.installing_widget.game:
self.installing_widget.setVisible(get_visibility(self.installing_widget)[0])
@pyqtSlot()
def sort_list(self, sort_by: str = ""):
# lk: this is the existing sorting implemenation
# lk: it sorts by installed then by title
installing_widget = self.icon_view.layout().remove(type(self.installing_widget).__name__)
if sort_by:
self.icon_view.layout().sort(lambda x: (sort_by.lower() not in x.widget().game.app_title.lower(),))
else:
self.icon_view.layout().sort(
lambda x: (
not x.widget().is_installed,
x.widget().is_non_asset,
x.widget().app_title,
)
)
self.icon_view.layout().insert(0, installing_widget)
list_widgets = self.list_view.findChildren(InstalledListWidget) + self.list_view.findChildren(
UninstalledListWidget)
if sort_by:
list_widgets.sort(key=lambda x: (sort_by not in x.game.app_title.lower(),))
else:
list_widgets.sort(
key=lambda x: (not x.is_installed, x.is_non_asset, x.app_title)
)
for idx, wl in enumerate(list_widgets):
self.list_view.layout().insertWidget(idx, wl)
def __remove_from_layout(self, app_name):
self.icon_view.layout().removeWidget(self.widgets[app_name][0])
self.list_view.layout().removeWidget(self.widgets[app_name][1])
self.widgets[app_name][0].deleteLater()
self.widgets[app_name][1].deleteLater()
self.widgets.pop(app_name)
def __update_installed(self, app_name):
self.__remove_from_layout(app_name)
icon_widget, list_widget = self.add_installed_widget(app_name)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
def __update_uninstalled(self, app_name):
self.__remove_from_layout(app_name)
game = self.core.get_game(app_name, False)
try:
icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
except Exception:
pass
def update_list(self, app_names: list = None):
logger.debug(f"Updating list for {app_names}")
if app_names:
update_list = False
for app_name in app_names:
if widgets := self.widgets.get(app_name):
# from update
if self.core.is_installed(widgets[0].game.app_name) and isinstance(
widgets[0], BaseInstalledWidget
):
logger.debug(f"Update Gamelist: Updated: {app_name}")
igame = self.core.get_installed_game(app_name)
for w in widgets:
w.igame = igame
w.update_available = (
self.core.get_asset(
w.game.app_name, w.igame.platform, False
).build_version
!= igame.version
)
widgets[0].leaveEvent(None)
# new installed
elif self.core.is_installed(app_name) and isinstance(
widgets[0], BaseUninstalledWidget
):
logger.debug(f"Update Gamelist: New installed {app_name}")
self.__update_installed(app_name)
update_list = True
# uninstalled
elif not self.core.is_installed(
widgets[0].game.app_name
) and isinstance(widgets[0], BaseInstalledWidget):
logger.debug(f"Update list: Uninstalled: {app_name}")
self.__update_uninstalled(app_name)
update_list = True
# do not update, if only update finished
if update_list:
self.sort_list()
else:
installed_names = [i.app_name for i in self.core.get_installed_list()]
# get Uninstalled games
uninstalled_names = []
games = self.core.get_game_list(True)
for game in sorted(games, key=lambda x: x.app_title):
if not game.app_name in installed_names:
uninstalled_names.append(game.app_name)
new_installed_games = list(
set(installed_names) - set([i.app_name for i in self.installed])
)
new_uninstalled_games = list(
set(uninstalled_names)
- set([i.app_name for i in self.uninstalled_games])
)
if (not new_uninstalled_games) and (not new_installed_games):
return
if new_installed_games:
for name in new_installed_games:
self.__update_installed(name)
for name in new_uninstalled_games:
self.__update_uninstalled(name)
self.sort_list()
self.update_count_games_label()
self.library_controller.filter_list(filter_name, search_text.lower())
def toggle_view(self):
self.settings.setValue("icon_view", not self.head_bar.view.isChecked())

View file

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

View file

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

View file

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

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

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 platform
from logging import getLogger
from typing import Optional
from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QFrame, QMessageBox, QAction
from PyQt5.QtCore import pyqtSignal, QSettings, QStandardPaths, Qt
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QMessageBox, QAction
from legendary.models.game import Game, InstalledGame
from rare.components.tabs.games.game_utils import GameUtils
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton
from rare.shared.image_manager import ImageSize
from rare.models.game import RareGame
from rare.shared import (
LegendaryCoreSingleton,
GlobalSignalsSingleton,
ArgumentsSingleton,
ImageManagerSingleton,
)
from rare.utils.misc import create_desktop_link
from rare.widgets.image_widget import ImageWidget
from .library_widget import LibraryWidget
logger = getLogger("Game")
logger = getLogger("BaseGameWidget")
class BaseInstalledWidget(QFrame):
launch_signal = pyqtSignal(str, QProcess, list)
show_info = pyqtSignal(str)
finish_signal = pyqtSignal(str, int)
proc: QProcess()
class GameWidget(LibraryWidget):
show_info = pyqtSignal(RareGame)
def __init__(self, app_name, pixmap: QPixmap, game_utils: GameUtils):
super(BaseInstalledWidget, self).__init__()
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
super(GameWidget, self).__init__(parent=parent)
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton()
self.image_manager = ImageManagerSingleton()
self.game_utils = game_utils
self.rgame = rgame
self.rgame.signals.widget.update.connect(
lambda: self.setPixmap(self.rgame.pixmap)
)
self.rgame.signals.progress.start.connect(
lambda: self.showProgress(
self.image_manager.get_pixmap(self.rgame.app_name, True),
self.image_manager.get_pixmap(self.rgame.app_name, False)
)
)
self.rgame.signals.progress.update.connect(
lambda p: self.updateProgress(p)
)
self.rgame.signals.progress.finish.connect(
lambda e: self.hideProgress(e)
)
self.rgame: RareGame = rgame
self.syncing_cloud_saves = False
self.game_running = False
self.settings = QSettings()
self.installing = False
self.setContextMenuPolicy(Qt.ActionsContextMenu)
launch = QAction(self.tr("Launch"), self)
launch.triggered.connect(self.launch)
self.addAction(launch)
if self.rgame.game.supports_cloud_saves:
sync = QAction(self.tr("Sync with cloud"), self)
sync.triggered.connect(self.sync_game)
self.addAction(sync)
desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
if os.path.exists(
os.path.join(desktop, f"{self.rgame.app_title}.desktop")
) or os.path.exists(os.path.join(desktop, f"{self.rgame.app_title}.lnk")):
self.create_desktop = QAction(self.tr("Remove Desktop link"))
else:
self.create_desktop = QAction(self.tr("Create Desktop link"))
if self.rgame.is_installed:
self.create_desktop.triggered.connect(
lambda: self.create_desktop_link("desktop")
)
self.addAction(self.create_desktop)
applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
if platform.system() == "Linux":
start_menu_file = os.path.join(applications, f"{self.rgame.app_title}.desktop")
elif platform.system() == "Windows":
start_menu_file = os.path.join(applications, "..", f"{self.rgame.app_title}.lnk")
else:
start_menu_file = ""
if platform.system() in ["Windows", "Linux"]:
if os.path.exists(start_menu_file):
self.create_start_menu = QAction(self.tr("Remove start menu link"))
else:
self.create_start_menu = QAction(self.tr("Create start menu link"))
if self.rgame.is_installed:
self.create_start_menu.triggered.connect(
lambda: self.create_desktop_link("start_menu")
)
self.addAction(self.create_start_menu)
reload_image = QAction(self.tr("Reload Image"), self)
reload_image.triggered.connect(self.reload_image)
self.addAction(reload_image)
if not self.rgame.is_origin:
uninstall = QAction(self.tr("Uninstall"), self)
self.addAction(uninstall)
uninstall.triggered.connect(
lambda: self.signals.game.uninstalled.emit(self.rgame.app_name)
if self.game_utils.uninstall_game(self.rgame.app_name)
else None
)
self.texts = {
"needs_verification": self.tr("Please verify game before playing"),
"static": {
"needs_verification": self.tr("Please verify game before playing"),
},
"hover": {
"update_available": self.tr("Start game without version check"),
"update_available": self.tr("Start without version check"),
"launch": self.tr("Launch Game"),
"launch_origin": self.tr("Launch/Link"),
"running": self.tr("Game running"),
@ -49,101 +133,60 @@ class BaseInstalledWidget(QFrame):
},
}
self.game = self.core.get_game(app_name)
self.igame = self.core.get_installed_game(app_name) # None if origin
if self.game.app_title == "Unreal Engine":
self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}"
self.is_only_offline = False
try:
self.core.get_asset(app_name, platform=self.igame.platform).build_version
except ValueError:
logger.warning(f"Game {self.game.app_title} has no metadata. Set offline true")
self.is_only_offline = True
except AttributeError:
pass
self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Display)
self.image.setPixmap(pixmap)
self.game_running = False
self.offline = self.args.offline
self.update_available = False
if self.igame and self.core.lgd.assets:
try:
remote_version = self.core.get_asset(
self.game.app_name, platform=self.igame.platform, update=False
).build_version
except ValueError:
logger.error(f"Asset error for {self.game.app_title}")
self.update_available = False
@property
def enterEventText(self) -> str:
if self.rgame.is_installed:
if self.game_running:
return self.texts["hover"]["running"]
elif (not self.rgame.is_origin) and self.rgame.needs_verification:
return self.texts["static"]["needs_verification"]
elif self.rgame.is_foreign:
return self.texts["hover"]["launch_offline"]
elif self.rgame.has_update:
return self.texts["hover"]["update_available"]
else:
if remote_version != self.igame.version:
self.update_available = True
self.data = QByteArray()
self.settings = QSettings()
self.setContextMenuPolicy(Qt.ActionsContextMenu)
launch = QAction(self.tr("Launch"), self)
launch.triggered.connect(self.launch)
self.addAction(launch)
if self.game.supports_cloud_saves:
sync = QAction(self.tr("Sync with cloud"), self)
sync.triggered.connect(self.sync_game)
self.addAction(sync)
desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
if os.path.exists(
os.path.join(desktop, f"{self.game.app_title}.desktop")
) or os.path.exists(os.path.join(desktop, f"{self.game.app_title}.lnk")):
self.create_desktop = QAction(self.tr("Remove Desktop link"))
return self.tr("Game Info")
# return self.texts["hover"]["launch" if self.igame else "launch_origin"]
else:
self.create_desktop = QAction(self.tr("Create Desktop link"))
if self.igame:
self.create_desktop.triggered.connect(
lambda: self.create_desktop_link("desktop")
)
self.addAction(self.create_desktop)
applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
if platform.system() == "Linux":
start_menu_file = os.path.join(applications, f"{self.game.app_title}.desktop")
elif platform.system() == "Windows":
start_menu_file = os.path.join(applications, "..", f"{self.game.app_title}.lnk")
else:
start_menu_file = ""
if platform.system() in ["Windows", "Linux"]:
if os.path.exists(start_menu_file):
self.create_start_menu = QAction(self.tr("Remove start menu link"))
if not self.installing:
return self.tr("Game Info")
else:
self.create_start_menu = QAction(self.tr("Create start menu link"))
if self.igame:
self.create_start_menu.triggered.connect(
lambda: self.create_desktop_link("start_menu")
)
self.addAction(self.create_start_menu)
return self.tr("Installation running")
reload_image = QAction(self.tr("Reload Image"), self)
reload_image.triggered.connect(self.reload_image)
self.addAction(reload_image)
@property
def leaveEventText(self) -> str:
if self.rgame.is_installed:
if self.game_running:
return self.texts["default"]["running"]
elif self.syncing_cloud_saves:
return self.texts["default"]["syncing"]
elif self.rgame.is_foreign:
return self.texts["default"]["no_meta"]
elif self.rgame.has_update:
return self.texts["default"]["update_available"]
elif (not self.rgame.is_origin) and self.rgame.needs_verification:
return self.texts["static"]["needs_verification"]
else:
return ""
else:
if self.installing:
return "Installation..."
else:
return ""
if self.igame is not None:
uninstall = QAction(self.tr("Uninstall"), self)
self.addAction(uninstall)
uninstall.triggered.connect(
lambda: self.signals.update_gamelist.emit([self.game.app_name])
if self.game_utils.uninstall_game(self.game.app_name)
else None
)
def mousePressEvent(self, e: QMouseEvent) -> None:
# left button
if e.button() == 1:
self.show_info.emit(self.rgame)
# right
elif e.button() == 2:
pass # self.showMenu(e)
def reload_image(self):
self.image_manager.download_image_blocking(self.game, force=True)
pm = self.image_manager.get_pixmap(self.game.app_name, color=True)
self.image.setPixmap(pm)
def reload_image(self) -> None:
self.rgame.refresh_pixmap()
def install(self):
self.show_info.emit(self.rgame)
def create_desktop_link(self, type_of_link):
if type_of_link == "desktop":
@ -154,9 +197,9 @@ class BaseInstalledWidget(QFrame):
return
if platform.system() == "Windows":
shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.lnk")
shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.lnk")
elif platform.system() == "Linux":
shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.desktop")
shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.desktop")
else:
QMessageBox.warning(
self,
@ -167,7 +210,7 @@ class BaseInstalledWidget(QFrame):
if not os.path.exists(shortcut_path):
try:
if not create_desktop_link(self.game.app_name, self.core, type_of_link):
if not create_desktop_link(self.rgame.app_name, self.core, type_of_link):
return
except PermissionError:
QMessageBox.warning(
@ -189,16 +232,16 @@ class BaseInstalledWidget(QFrame):
def launch(self, offline=False, skip_version_check=False):
if self.game_running:
return
offline = offline or self.is_only_offline
if self.is_only_offline and not self.igame.can_run_offline:
offline = offline or self.rgame.is_foreign
if self.rgame.is_foreign and not self.rgame.can_run_offline:
QMessageBox.warning(self, "Warning",
self.tr("This game is probably not in your library and it cannot be launched offline"))
return
if self.game.supports_cloud_saves and not offline:
if self.rgame.game.supports_cloud_saves and not offline:
self.syncing_cloud_saves = True
self.game_utils.prepare_launch(
self.game.app_name, offline, skip_version_check
self.rgame, offline, skip_version_check
)
def sync_finished(self, app_name):
@ -207,7 +250,7 @@ class BaseInstalledWidget(QFrame):
def sync_game(self):
try:
sync = self.game_utils.cloud_save_utils.sync_before_launch_game(
self.game.app_name, True
self.rgame.app_name, True
)
except Exception:
sync = False
@ -218,37 +261,3 @@ class BaseInstalledWidget(QFrame):
if error:
QMessageBox.warning(self, "Error", error)
self.game_running = False
# From RareGame, added from sorting to work
@property
def is_non_asset(self) -> bool:
"""!
@brief Property to report if a Game doesn't have assets
Typically, games have assets, however some games that require
other launchers do not have them. Rare treats these games as installed
offering to execute their launcher.
@return bool If the game doesn't have assets
"""
return not self.game.asset_infos
@property
def is_origin(self) -> bool:
return self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin"
@property
def is_installed(self) -> bool:
"""!
@brief Property to report if a game is installed
This returns True if InstalledGame data have been loaded for the game
or if the game is a game without assets, for example an Origin game.
@return bool If the game should be considered installed
"""
return (self.igame is not None) or self.is_non_asset
@property
def app_title(self) -> str:
return self.igame.title if self.igame is not None else self.game.app_title

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.signals = GlobalSignalsSingleton()
self.signals.game_uninstalled.connect(self.remove_button)
self.signals.game.uninstalled.connect(self.remove_button)
def remove_button(self, app_name: str):
if action := next((i for i in self.game_actions if i.property("app_name") == app_name), None):

View file

@ -71,17 +71,17 @@ class RareGame(QObject):
progress: int = 0
active_thread: Optional[QRunnable] = None
def __init__(self, legendary_core: LegendaryCore, image_manager: ImageManager, game: Game):
def __init__(self, game: Game, legendary_core: LegendaryCore, image_manager: ImageManager):
super(RareGame, self).__init__()
self.signals = RareGame.Signals()
self.core = legendary_core
self.image_manager = image_manager
# Update names for Unreal Engine
if game.app_title == "Unreal Engine":
game.app_title += f" {game.app_name.split('_')[-1]}"
self.game: Game = game
# Update names for Unreal Engine
if self.game.app_title == "Unreal Engine":
self.game.app_title += f" {self.game.app_name.split('_')[-1]}"
# None if origin or not installed
self.igame: Optional[InstalledGame] = self.core.get_installed_game(game.app_name)

View file

@ -10,18 +10,34 @@ class GlobalSignals(QObject):
set_main_tab_index = pyqtSignal(int) # tab index
update_download_tab_text = pyqtSignal()
dl_progress = pyqtSignal(int) # 0-100
# set visibility of installing widget in games tab
installation_started = pyqtSignal(str) # app_name
add_download = pyqtSignal(str)
class ProgressSignals(QObject):
# str: app_name
started = pyqtSignal(str)
# str: app_name, int: progress
value = pyqtSignal(str, int)
# str: app_name, bool: stopped
finished = pyqtSignal(str, bool)
progress = ProgressSignals()
install_game = pyqtSignal(InstallOptionsModel)
installation_finished = pyqtSignal(bool, str)
class GameSignals(QObject):
install = pyqtSignal(InstallOptionsModel)
# list of app_name
installed = pyqtSignal(list)
# str: app_name
uninstalled = pyqtSignal(str)
# str: app_name
verified = pyqtSignal(str)
game = GameSignals()
class DownloadSignals(QObject):
# str: app_name
enqueue_game = pyqtSignal(str)
download = DownloadSignals()
overlay_installation_finished = pyqtSignal()
update_gamelist = pyqtSignal(list)
game_uninstalled = pyqtSignal(str) # appname
# update_gamelist = pyqtSignal(list)
# game_uninstalled = pyqtSignal(str)
set_discord_rpc = pyqtSignal(str) # app_name of running game
rpc_settings_updated = pyqtSignal()