Merge pull request #211 from loathingKernel/image_manager
Implement image manager
This commit is contained in:
commit
8888cb3aee
21 changed files with 652 additions and 296 deletions
15
rare/app.py
15
rare/app.py
|
@ -8,22 +8,24 @@ import time
|
||||||
import traceback
|
import traceback
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import legendary
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
from PyQt5.QtCore import Qt, QThreadPool, QSettings, QTranslator, QTimer
|
from PyQt5.QtCore import Qt, QThreadPool, QSettings, QTranslator, QTimer
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
|
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
|
|
||||||
import legendary
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
import rare.resources.resources
|
import rare.resources.resources
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
|
||||||
from rare.utils.paths import cache_dir, resources_path, tmp_dir
|
|
||||||
from rare.components.dialogs.launch_dialog import LaunchDialog
|
from rare.components.dialogs.launch_dialog import LaunchDialog
|
||||||
from rare.components.main_window import MainWindow
|
from rare.components.main_window import MainWindow
|
||||||
from rare.components.tray_icon import TrayIcon
|
from rare.components.tray_icon import TrayIcon
|
||||||
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton
|
||||||
from rare.utils import legendary_utils, config_helper
|
from rare.utils import legendary_utils, config_helper
|
||||||
|
from rare.utils.paths import cache_dir, resources_path, tmp_dir
|
||||||
from rare.utils.utils import set_color_pallete, set_style_sheet
|
from rare.utils.utils import set_color_pallete, set_style_sheet
|
||||||
|
|
||||||
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
|
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
|
||||||
|
@ -54,8 +56,8 @@ def excepthook(exc_type, exc_value, exc_tb):
|
||||||
|
|
||||||
|
|
||||||
class App(QApplication):
|
class App(QApplication):
|
||||||
mainwindow: MainWindow = None
|
mainwindow: Optional[MainWindow] = None
|
||||||
tray_icon: QSystemTrayIcon = None
|
tray_icon: Optional[QSystemTrayIcon] = None
|
||||||
|
|
||||||
def __init__(self, args: Namespace):
|
def __init__(self, args: Namespace):
|
||||||
super(App, self).__init__(sys.argv)
|
super(App, self).__init__(sys.argv)
|
||||||
|
@ -63,7 +65,7 @@ class App(QApplication):
|
||||||
self.window_launched = False
|
self.window_launched = False
|
||||||
self.setQuitOnLastWindowClosed(False)
|
self.setQuitOnLastWindowClosed(False)
|
||||||
|
|
||||||
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
|
if hasattr(Qt, "AA_UseHighDpiPixmaps"):
|
||||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
self.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
||||||
|
|
||||||
# init Legendary
|
# init Legendary
|
||||||
|
@ -102,6 +104,7 @@ class App(QApplication):
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
|
|
||||||
self.signals = GlobalSignalsSingleton(init=True)
|
self.signals = GlobalSignalsSingleton(init=True)
|
||||||
|
self.image_manager = ImageManagerSingleton(init=True)
|
||||||
|
|
||||||
self.signals.exit_app.connect(self.exit_app)
|
self.signals.exit_app.connect(self.exit_app)
|
||||||
self.signals.send_notification.connect(
|
self.signals.send_notification.connect(
|
||||||
|
|
|
@ -1,45 +1,77 @@
|
||||||
import os
|
|
||||||
import platform
|
import platform
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings
|
from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings
|
||||||
from PyQt5.QtWidgets import QDialog, QApplication
|
from PyQt5.QtWidgets import QDialog, QApplication
|
||||||
from legendary.core import LegendaryCore
|
|
||||||
from requests.exceptions import ConnectionError, HTTPError
|
from requests.exceptions import ConnectionError, HTTPError
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton
|
|
||||||
from rare.components.dialogs.login import LoginDialog
|
from rare.components.dialogs.login import LoginDialog
|
||||||
|
from rare.models.apiresults import ApiResults
|
||||||
|
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton
|
||||||
from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog
|
from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog
|
||||||
from rare.utils.models import ApiResults
|
from rare.utils.utils import CloudWorker
|
||||||
from rare.utils.paths import image_dir
|
|
||||||
from rare.utils.utils import download_images, CloudWorker
|
|
||||||
|
|
||||||
logger = getLogger("Login")
|
logger = getLogger("Login")
|
||||||
|
|
||||||
|
|
||||||
class LaunchDialogSignals(QObject):
|
class LaunchWorker(QRunnable):
|
||||||
image_progress = pyqtSignal(int)
|
class Signals(QObject):
|
||||||
|
progress = pyqtSignal(int)
|
||||||
result = pyqtSignal(object, str)
|
result = pyqtSignal(object, str)
|
||||||
|
|
||||||
|
|
||||||
class ImageWorker(QRunnable):
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ImageWorker, self).__init__()
|
super(LaunchWorker, self).__init__()
|
||||||
self.signals = LaunchDialogSignals()
|
|
||||||
self.setAutoDelete(True)
|
self.setAutoDelete(True)
|
||||||
|
self.signals = LaunchWorker.Signals()
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
download_images(self.signals.image_progress, self.signals.result, self.core)
|
pass
|
||||||
self.signals.image_progress.emit(100)
|
|
||||||
|
|
||||||
|
|
||||||
class ApiRequestWorker(QRunnable):
|
class ImageWorker(LaunchWorker):
|
||||||
|
def __init__(self):
|
||||||
|
super(ImageWorker, self).__init__()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Download Images
|
||||||
|
games, dlcs = self.core.get_game_and_dlc_list(update_assets=True, skip_ue=False)
|
||||||
|
self.signals.result.emit((games, dlcs), "gamelist")
|
||||||
|
dlc_list = [dlc[0] for dlc in dlcs.values()]
|
||||||
|
|
||||||
|
na_games, na_dlcs = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
|
||||||
|
self.signals.result.emit(na_games, "no_assets")
|
||||||
|
na_dlc_list = [dlc[0] for dlc in na_dlcs.values()]
|
||||||
|
|
||||||
|
game_list = games + dlc_list + na_games + na_dlc_list
|
||||||
|
fetched = [False] * len(game_list)
|
||||||
|
|
||||||
|
def set_fetched(idx):
|
||||||
|
fetched[idx] = True
|
||||||
|
self.signals.progress.emit(sum(fetched))
|
||||||
|
|
||||||
|
for i, game in enumerate(game_list):
|
||||||
|
if game.app_title == "Unreal Engine":
|
||||||
|
game.app_title += f" {game.app_name.split('_')[-1]}"
|
||||||
|
self.core.lgd.set_game_meta(game.app_name, game)
|
||||||
|
self.image_manager.download_image_blocking(game)
|
||||||
|
self.signals.progress.emit(int(i / len(game_list) * 100))
|
||||||
|
# self.image_manager.download_image(
|
||||||
|
# game,
|
||||||
|
# load_callback=lambda: set_fetched(i),
|
||||||
|
# priority=i
|
||||||
|
# )
|
||||||
|
# while not all(fetched):
|
||||||
|
# continue
|
||||||
|
|
||||||
|
self.signals.progress.emit(100)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiRequestWorker(LaunchWorker):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApiRequestWorker, self).__init__()
|
super(ApiRequestWorker, self).__init__()
|
||||||
self.signals = LaunchDialogSignals()
|
|
||||||
self.setAutoDelete(True)
|
|
||||||
self.core = LegendaryCoreSingleton()
|
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
@ -106,14 +138,11 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
|
||||||
self.quit_app.emit(0)
|
self.quit_app.emit(0)
|
||||||
|
|
||||||
def launch(self):
|
def launch(self):
|
||||||
# self.core = core
|
|
||||||
if not os.path.exists(image_dir):
|
|
||||||
os.makedirs(image_dir)
|
|
||||||
|
|
||||||
if not self.args.offline:
|
if not self.args.offline:
|
||||||
self.image_info.setText(self.tr("Downloading Images"))
|
self.image_info.setText(self.tr("Downloading Images"))
|
||||||
image_worker = ImageWorker()
|
image_worker = ImageWorker()
|
||||||
image_worker.signals.image_progress.connect(self.update_image_progbar)
|
image_worker.signals.progress.connect(self.update_image_progbar)
|
||||||
image_worker.signals.result.connect(self.handle_api_worker_result)
|
image_worker.signals.result.connect(self.handle_api_worker_result)
|
||||||
self.thread_pool.start(image_worker)
|
self.thread_pool.start(image_worker)
|
||||||
|
|
||||||
|
@ -124,9 +153,7 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
|
||||||
|
|
||||||
# cloud save from another worker, because it is used in cloud_save_utils too
|
# cloud save from another worker, because it is used in cloud_save_utils too
|
||||||
cloud_worker = CloudWorker()
|
cloud_worker = CloudWorker()
|
||||||
cloud_worker.signals.result_ready.connect(
|
cloud_worker.signals.result_ready.connect(lambda x: self.handle_api_worker_result(x, "saves"))
|
||||||
lambda x: self.handle_api_worker_result(x, "saves")
|
|
||||||
)
|
|
||||||
self.thread_pool.start(cloud_worker)
|
self.thread_pool.start(cloud_worker)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -159,13 +186,9 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
|
||||||
self.api_results.dlcs,
|
self.api_results.dlcs,
|
||||||
) = self.core.get_game_and_dlc_list(False)
|
) = self.core.get_game_and_dlc_list(False)
|
||||||
elif text == "32bit":
|
elif text == "32bit":
|
||||||
self.api_results.bit32_games = (
|
self.api_results.bit32_games = [i.app_name for i in result[0]] if result else []
|
||||||
[i.app_name for i in result[0]] if result else []
|
|
||||||
)
|
|
||||||
elif text == "mac":
|
elif text == "mac":
|
||||||
self.api_results.mac_games = (
|
self.api_results.mac_games = [i.app_name for i in result[0]] if result else []
|
||||||
[i.app_name for i in result[0]] if result else []
|
|
||||||
)
|
|
||||||
|
|
||||||
elif text == "no_assets":
|
elif text == "no_assets":
|
||||||
self.api_results.no_asset_games = result if result else []
|
self.api_results.no_asset_games = result if result else []
|
||||||
|
|
|
@ -3,12 +3,17 @@ from typing import Tuple, Dict, Union, List
|
||||||
|
|
||||||
from PyQt5.QtCore import QSettings, QObjectCleanupHandler
|
from PyQt5.QtCore import QSettings, QObjectCleanupHandler
|
||||||
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget
|
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ApiResultsSingleton
|
|
||||||
from legendary.models.game import InstalledGame, Game
|
from legendary.models.game import InstalledGame, Game
|
||||||
|
|
||||||
|
from rare.shared import (
|
||||||
|
LegendaryCoreSingleton,
|
||||||
|
GlobalSignalsSingleton,
|
||||||
|
ArgumentsSingleton,
|
||||||
|
ApiResultsSingleton,
|
||||||
|
)
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton
|
||||||
from rare.ui.components.tabs.games.games_tab import Ui_GamesTab
|
from rare.ui.components.tabs.games.games_tab import Ui_GamesTab
|
||||||
from rare.utils.extra_widgets import FlowLayout
|
from rare.utils.extra_widgets import FlowLayout
|
||||||
from rare.utils.utils import get_pixmap, download_image, get_uninstalled_pixmap
|
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
from .cloud_save_utils import CloudSaveUtils
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
from .cloud_save_utils import CloudSaveUtils
|
||||||
from .game_info import GameInfoTabs
|
from .game_info import GameInfoTabs
|
||||||
|
@ -41,6 +46,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = GlobalSignalsSingleton()
|
||||||
self.args = ArgumentsSingleton()
|
self.args = ArgumentsSingleton()
|
||||||
self.api_results = ApiResultsSingleton()
|
self.api_results = ApiResultsSingleton()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
|
|
||||||
self.game_list: List[Game] = self.api_results.game_list
|
self.game_list: List[Game] = self.api_results.game_list
|
||||||
|
@ -71,7 +77,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
||||||
|
|
||||||
for i in self.game_list:
|
for i in self.game_list:
|
||||||
if i.app_name.startswith("UE_4"):
|
if i.app_name.startswith("UE_4"):
|
||||||
pixmap = get_pixmap(i.app_name)
|
pixmap = self.image_manager.get_pixmap(i.app_name)
|
||||||
if pixmap.isNull():
|
if pixmap.isNull():
|
||||||
continue
|
continue
|
||||||
self.ue_name = i.app_name
|
self.ue_name = i.app_name
|
||||||
|
@ -216,15 +222,15 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_installed_widget(self, app_name):
|
def add_installed_widget(self, app_name):
|
||||||
pixmap = get_pixmap(app_name)
|
pixmap = self.image_manager.get_pixmap(app_name)
|
||||||
try:
|
try:
|
||||||
if pixmap.isNull():
|
if pixmap.isNull():
|
||||||
logger.info(f"{app_name} has a corrupt image.")
|
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":
|
if app_name in self.no_asset_names and self.core.get_asset(app_name).namespace != "ue":
|
||||||
download_image(self.core.get_game(app_name), force=True)
|
self.image_manager.download_image_blocking(self.core.get_game(app_name), force=True)
|
||||||
pixmap = get_pixmap(app_name)
|
pixmap = self.image_manager.get_pixmap(app_name)
|
||||||
elif self.ue_name:
|
elif self.ue_name:
|
||||||
pixmap = get_pixmap(self.ue_name)
|
pixmap = self.image_manager.get_pixmap(self.ue_name)
|
||||||
|
|
||||||
icon_widget = InstalledIconWidget(app_name, pixmap, self.game_utils)
|
icon_widget = InstalledIconWidget(app_name, pixmap, self.game_utils)
|
||||||
list_widget = InstalledListWidget(app_name, pixmap, self.game_utils)
|
list_widget = InstalledListWidget(app_name, pixmap, self.game_utils)
|
||||||
|
@ -242,15 +248,15 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
||||||
return icon_widget, list_widget
|
return icon_widget, list_widget
|
||||||
|
|
||||||
def add_uninstalled_widget(self, game):
|
def add_uninstalled_widget(self, game):
|
||||||
pixmap = get_uninstalled_pixmap(game.app_name)
|
pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
|
||||||
try:
|
try:
|
||||||
if pixmap.isNull():
|
if pixmap.isNull():
|
||||||
if self.core.get_asset(game.app_name).namespace != "ue":
|
if self.core.get_asset(game.app_name).namespace != "ue":
|
||||||
logger.warning(f"{game.app_title} has a corrupt image. Reloading...")
|
logger.warning(f"{game.app_title} has a corrupt image. Reloading...")
|
||||||
download_image(game, force=True)
|
self.image_manager.download_image_blocking(game, force=True)
|
||||||
pixmap = get_uninstalled_pixmap(game.app_name)
|
pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
|
||||||
elif self.ue_name:
|
elif self.ue_name:
|
||||||
pixmap = get_uninstalled_pixmap(self.ue_name)
|
pixmap = self.image_manager.get_pixmap(self.ue_name, color=False)
|
||||||
|
|
||||||
icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
|
icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
|
||||||
list_widget = ListWidgetUninstalled(self.core, game, pixmap)
|
list_widget = ListWidgetUninstalled(self.core, game, pixmap)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal
|
from PyQt5.QtCore import Qt, pyqtSignal
|
||||||
from PyQt5.QtGui import QPixmap, QResizeEvent
|
from PyQt5.QtGui import QPixmap, QResizeEvent
|
||||||
from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox
|
from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox
|
||||||
|
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
|
||||||
from rare.components.tabs.games.game_utils import GameUtils
|
from rare.components.tabs.games.game_utils import GameUtils
|
||||||
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, 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
|
||||||
from rare.utils.models import InstallOptionsModel
|
from rare.utils.models import InstallOptionsModel
|
||||||
from rare.utils.utils import get_pixmap
|
|
||||||
|
|
||||||
|
|
||||||
class GameDlc(QWidget, Ui_GameDlc):
|
class GameDlc(QWidget, Ui_GameDlc):
|
||||||
|
@ -90,14 +90,17 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget):
|
||||||
|
|
||||||
def __init__(self, dlc: Game, installed: bool, parent=None):
|
def __init__(self, dlc: Game, installed: bool, parent=None):
|
||||||
super(GameDlcWidget, self).__init__(parent=parent)
|
super(GameDlcWidget, self).__init__(parent=parent)
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.dlc = dlc
|
self.dlc = dlc
|
||||||
|
|
||||||
|
self.image.setFixedSize(ImageSize.Smaller.size)
|
||||||
|
|
||||||
self.dlc_name.setText(dlc.app_title)
|
self.dlc_name.setText(dlc.app_title)
|
||||||
self.version.setText(dlc.app_version())
|
self.version.setText(dlc.app_version())
|
||||||
self.app_name.setText(dlc.app_name)
|
self.app_name.setText(dlc.app_name)
|
||||||
|
|
||||||
self.pixmap = get_pixmap(dlc.app_name)
|
self.pixmap = self.image_manager.get_pixmap(dlc.app_name)
|
||||||
|
|
||||||
if installed:
|
if installed:
|
||||||
self.action_button.setProperty("uninstall", 1)
|
self.action_button.setProperty("uninstall", 1)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from pathlib import Path
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
|
@ -25,20 +25,21 @@ from PyQt5.QtWidgets import (
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QWidgetAction,
|
QWidgetAction,
|
||||||
)
|
)
|
||||||
|
|
||||||
from legendary.models.game import Game, InstalledGame, VerifyResult
|
from legendary.models.game import Game, InstalledGame, VerifyResult
|
||||||
from rare.legendary.legendary.utils.lfs import validate_files
|
from legendary.utils.lfs import validate_files
|
||||||
|
|
||||||
from rare.shared import (
|
from rare.shared import (
|
||||||
LegendaryCoreSingleton,
|
LegendaryCoreSingleton,
|
||||||
GlobalSignalsSingleton,
|
GlobalSignalsSingleton,
|
||||||
ArgumentsSingleton,
|
ArgumentsSingleton,
|
||||||
)
|
)
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||||
from rare.utils.extra_widgets import PathEdit
|
from rare.utils.extra_widgets import PathEdit
|
||||||
from rare.utils.legendary_utils import VerifyWorker
|
from rare.utils.legendary_utils import VerifyWorker
|
||||||
from rare.utils.models import InstallOptionsModel
|
from rare.utils.models import InstallOptionsModel
|
||||||
from rare.utils.steam_grades import SteamWorker
|
from rare.utils.steam_grades import SteamWorker
|
||||||
from rare.utils.utils import get_size, get_pixmap
|
from rare.utils.utils import get_size
|
||||||
|
|
||||||
logger = getLogger("GameInfo")
|
logger = getLogger("GameInfo")
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ class GameInfo(QWidget, Ui_GameInfo):
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = GlobalSignalsSingleton()
|
||||||
self.args = ArgumentsSingleton()
|
self.args = ArgumentsSingleton()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
self.game_utils = game_utils
|
self.game_utils = game_utils
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
|
@ -281,11 +283,10 @@ class GameInfo(QWidget, Ui_GameInfo):
|
||||||
self.igame = self.core.get_installed_game(self.game.app_name)
|
self.igame = self.core.get_installed_game(self.game.app_name)
|
||||||
self.title.setTitle(self.game.app_title)
|
self.title.setTitle(self.game.app_title)
|
||||||
|
|
||||||
pixmap = get_pixmap(self.game.app_name)
|
pixmap = self.image_manager.get_pixmap(self.game.app_name)
|
||||||
if pixmap.isNull():
|
if pixmap.isNull():
|
||||||
pixmap = get_pixmap(self.parent().parent().parent().ue_name)
|
pixmap = self.image_manager.get_pixmap(self.parent().parent().parent().ue_name)
|
||||||
w = 200
|
pixmap = pixmap.scaled(ImageSize.Display.size)
|
||||||
pixmap = pixmap.scaled(w, int(w * 4 / 3))
|
|
||||||
self.image.setPixmap(pixmap)
|
self.image.setPixmap(pixmap)
|
||||||
|
|
||||||
self.app_name.setText(self.game.app_name)
|
self.app_name.setText(self.game.app_name)
|
||||||
|
|
|
@ -3,15 +3,20 @@ import platform
|
||||||
from PyQt5.QtCore import Qt, QThreadPool
|
from PyQt5.QtCore import Qt, QThreadPool
|
||||||
from PyQt5.QtGui import QKeyEvent
|
from PyQt5.QtGui import QKeyEvent
|
||||||
from PyQt5.QtWidgets import QWidget, QTreeView
|
from PyQt5.QtWidgets import QWidget, QTreeView
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ApiResultsSingleton
|
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
|
|
||||||
|
from rare.shared import (
|
||||||
|
LegendaryCoreSingleton,
|
||||||
|
GlobalSignalsSingleton,
|
||||||
|
ArgumentsSingleton,
|
||||||
|
ApiResultsSingleton,
|
||||||
|
)
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||||
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
|
||||||
from rare.utils.models import InstallOptionsModel
|
from rare.utils.models import InstallOptionsModel
|
||||||
from rare.utils.steam_grades import SteamWorker
|
from rare.utils.steam_grades import SteamWorker
|
||||||
from rare.utils.utils import get_pixmap
|
|
||||||
|
|
||||||
|
|
||||||
class UninstalledInfoTabs(SideTabWidget):
|
class UninstalledInfoTabs(SideTabWidget):
|
||||||
|
@ -71,6 +76,8 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = GlobalSignalsSingleton()
|
||||||
self.api_results = ApiResultsSingleton()
|
self.api_results = ApiResultsSingleton()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
|
||||||
self.install_button.clicked.connect(self.install_game)
|
self.install_button.clicked.connect(self.install_game)
|
||||||
if platform.system() != "Windows":
|
if platform.system() != "Windows":
|
||||||
self.steam_worker = SteamWorker(self.core)
|
self.steam_worker = SteamWorker(self.core)
|
||||||
|
@ -104,11 +111,10 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
|
||||||
available_platforms.append("macOS")
|
available_platforms.append("macOS")
|
||||||
self.platform.setText(", ".join(available_platforms))
|
self.platform.setText(", ".join(available_platforms))
|
||||||
|
|
||||||
pixmap = get_pixmap(game.app_name)
|
pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
|
||||||
if pixmap.isNull():
|
if pixmap.isNull():
|
||||||
pixmap = get_pixmap(self.ue_default_name)
|
pixmap = self.image_manager.get_pixmap(self.ue_default_name, color=False)
|
||||||
w = 200
|
pixmap = pixmap.scaled(ImageSize.Display.size)
|
||||||
pixmap = pixmap.scaled(w, int(w * 4 / 3))
|
|
||||||
self.image.setPixmap(pixmap)
|
self.image.setPixmap(pixmap)
|
||||||
|
|
||||||
self.app_name.setText(self.game.app_name)
|
self.app_name.setText(self.game.app_name)
|
||||||
|
|
|
@ -6,9 +6,9 @@ from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QB
|
||||||
from PyQt5.QtGui import QPixmap
|
from PyQt5.QtGui import QPixmap
|
||||||
from PyQt5.QtWidgets import QGroupBox, QMessageBox, QAction, QLabel
|
from PyQt5.QtWidgets import QGroupBox, QMessageBox, QAction, QLabel
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
|
||||||
from rare.components.tabs.games.game_utils import GameUtils
|
from rare.components.tabs.games.game_utils import GameUtils
|
||||||
from rare.utils import utils
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
from rare.utils.utils import create_desktop_link
|
from rare.utils.utils import create_desktop_link
|
||||||
|
|
||||||
logger = getLogger("Game")
|
logger = getLogger("Game")
|
||||||
|
@ -25,6 +25,7 @@ class BaseInstalledWidget(QGroupBox):
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = GlobalSignalsSingleton()
|
||||||
self.args = ArgumentsSingleton()
|
self.args = ArgumentsSingleton()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
self.game_utils = game_utils
|
self.game_utils = game_utils
|
||||||
|
|
||||||
self.syncing_cloud_saves = False
|
self.syncing_cloud_saves = False
|
||||||
|
@ -63,8 +64,9 @@ class BaseInstalledWidget(QGroupBox):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.image = QLabel()
|
self.image = QLabel()
|
||||||
|
self.image.setFixedSize(ImageSize.Display.size)
|
||||||
self.image.setPixmap(
|
self.image.setPixmap(
|
||||||
pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
pixmap.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation)
|
||||||
)
|
)
|
||||||
self.game_running = False
|
self.game_running = False
|
||||||
self.offline = self.args.offline
|
self.offline = self.args.offline
|
||||||
|
@ -140,11 +142,9 @@ class BaseInstalledWidget(QGroupBox):
|
||||||
)
|
)
|
||||||
|
|
||||||
def reload_image(self):
|
def reload_image(self):
|
||||||
utils.download_image(self.game, True)
|
self.image_manager.download_image_blocking(self.game, force=True)
|
||||||
pm = utils.get_pixmap(self.game.app_name)
|
pm = self.image_manager.get_pixmap(self.game.app_name, color=True)
|
||||||
self.image.setPixmap(
|
self.image.setPixmap(pm.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
|
||||||
pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
|
||||||
)
|
|
||||||
|
|
||||||
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":
|
||||||
|
|
|
@ -2,9 +2,9 @@ from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt
|
from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
from PyQt5.QtWidgets import QGroupBox, QLabel, QAction
|
from PyQt5.QtWidgets import QGroupBox, QLabel, QAction
|
||||||
|
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
from rare.utils import utils
|
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
|
|
||||||
logger = getLogger("Uninstalled")
|
logger = getLogger("Uninstalled")
|
||||||
|
|
||||||
|
@ -14,15 +14,16 @@ class BaseUninstalledWidget(QGroupBox):
|
||||||
|
|
||||||
def __init__(self, game, core, pixmap):
|
def __init__(self, game, core, pixmap):
|
||||||
super(BaseUninstalledWidget, self).__init__()
|
super(BaseUninstalledWidget, self).__init__()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
|
||||||
self.game = game
|
self.game = game
|
||||||
if self.game.app_title == "Unreal Engine":
|
if self.game.app_title == "Unreal Engine":
|
||||||
self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}"
|
self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}"
|
||||||
|
|
||||||
self.core = core
|
self.core = core
|
||||||
self.image = QLabel()
|
self.image = QLabel()
|
||||||
self.image.setPixmap(
|
self.image.setFixedSize(ImageSize.Display.size)
|
||||||
pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
self.image.setPixmap(pixmap.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
|
||||||
)
|
|
||||||
self.installing = False
|
self.installing = False
|
||||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
|
@ -32,11 +33,9 @@ class BaseUninstalledWidget(QGroupBox):
|
||||||
self.addAction(reload_image)
|
self.addAction(reload_image)
|
||||||
|
|
||||||
def reload_image(self):
|
def reload_image(self):
|
||||||
utils.download_image(self.game, True)
|
self.image_manager.download_image_blocking(self.game, force=True)
|
||||||
pm = utils.get_uninstalled_pixmap(self.game.app_name)
|
pm = self.image_manager.get_pixmap(self.game.app_name, color=False)
|
||||||
self.image.setPixmap(
|
self.image.setPixmap(pm.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
|
||||||
pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
|
||||||
)
|
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
self.show_uninstalled_info.emit(self.game)
|
self.show_uninstalled_info.emit(self.game)
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import QEvent, pyqtSignal, QSize, Qt
|
from PyQt5.QtCore import QEvent, pyqtSignal, Qt
|
||||||
from PyQt5.QtGui import QMouseEvent
|
from PyQt5.QtGui import QMouseEvent
|
||||||
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLabel
|
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QWidget
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton
|
|
||||||
from rare.components.tabs.games.game_widgets.base_installed_widget import (
|
from rare.components.tabs.games.game_widgets.base_installed_widget import (
|
||||||
BaseInstalledWidget,
|
BaseInstalledWidget,
|
||||||
)
|
)
|
||||||
|
from rare.shared import LegendaryCoreSingleton
|
||||||
|
from rare.shared.image_manager import ImageSize
|
||||||
from rare.utils.utils import icon
|
from rare.utils.utils import icon
|
||||||
|
from rare.widgets.elide_label import ElideLabel
|
||||||
|
|
||||||
logger = getLogger("GameWidgetInstalled")
|
logger = getLogger("GameWidgetInstalled")
|
||||||
|
|
||||||
|
@ -21,55 +23,54 @@ class InstalledIconWidget(BaseInstalledWidget):
|
||||||
self.setObjectName("game_widget_icon")
|
self.setObjectName("game_widget_icon")
|
||||||
|
|
||||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||||
self.layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
|
|
||||||
if self.update_available:
|
if self.update_available:
|
||||||
logger.info(f"Update available for game: {self.game.app_name}")
|
logger.info(f"Update available for game: {self.game.app_name}")
|
||||||
|
|
||||||
self.layout.addWidget(self.image)
|
layout.addWidget(self.image)
|
||||||
|
|
||||||
self.game_utils.finished.connect(self.game_finished)
|
self.game_utils.finished.connect(self.game_finished)
|
||||||
self.game_utils.cloud_save_finished.connect(self.sync_finished)
|
self.game_utils.cloud_save_finished.connect(self.sync_finished)
|
||||||
|
|
||||||
self.title_label = QLabel(f"<h4>{self.game.app_title}</h4>")
|
miniwidget = QWidget(self)
|
||||||
self.title_label.setAutoFillBackground(False)
|
miniwidget.setFixedWidth(ImageSize.Display.size.width())
|
||||||
self.title_label.setWordWrap(True)
|
|
||||||
self.title_label.setFixedWidth(175)
|
|
||||||
minilayout = QHBoxLayout()
|
minilayout = QHBoxLayout()
|
||||||
|
minilayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
minilayout.setSpacing(0)
|
||||||
|
miniwidget.setLayout(minilayout)
|
||||||
|
|
||||||
|
self.title_label = ElideLabel(f"<h4>{self.game.app_title}</h4>", parent=miniwidget)
|
||||||
self.title_label.setObjectName("game_widget")
|
self.title_label.setObjectName("game_widget")
|
||||||
minilayout.addWidget(self.title_label)
|
minilayout.addWidget(self.title_label, stretch=2)
|
||||||
|
|
||||||
# Info Button
|
# Info Button
|
||||||
self.menu_btn = QPushButton()
|
self.menu_btn = QPushButton(parent=miniwidget)
|
||||||
self.menu_btn.setIcon(icon("ei.info-circle"))
|
self.menu_btn.setIcon(icon("ei.info-circle"))
|
||||||
# self.menu_btn.setObjectName("installed_menu_button")
|
# self.menu_btn.setObjectName("installed_menu_button")
|
||||||
self.menu_btn.setIconSize(QSize(18, 18))
|
self.menu_btn.enterEvent = lambda x: self.info_label.setText(self.tr("Information"))
|
||||||
self.menu_btn.enterEvent = lambda x: self.info_label.setText(
|
|
||||||
self.tr("Information")
|
|
||||||
)
|
|
||||||
self.menu_btn.leaveEvent = lambda x: self.enterEvent(None)
|
self.menu_btn.leaveEvent = lambda x: self.enterEvent(None)
|
||||||
# remove Border
|
# remove Border
|
||||||
|
|
||||||
self.menu_btn.setObjectName("menu_button")
|
self.menu_btn.setObjectName("menu_button")
|
||||||
|
|
||||||
self.menu_btn.clicked.connect(lambda: self.show_info.emit(self.game.app_name))
|
self.menu_btn.clicked.connect(lambda: self.show_info.emit(self.game.app_name))
|
||||||
self.menu_btn.setFixedWidth(17)
|
self.menu_btn.setFixedSize(22, 22)
|
||||||
minilayout.addWidget(self.menu_btn)
|
minilayout.addWidget(self.menu_btn, stretch=0)
|
||||||
minilayout.addStretch(1)
|
minilayout.setAlignment(Qt.AlignTop)
|
||||||
self.layout.addLayout(minilayout)
|
layout.addWidget(miniwidget)
|
||||||
|
|
||||||
self.info_label = QLabel("")
|
self.info_label = ElideLabel(" ", parent=self)
|
||||||
|
self.info_label.setFixedWidth(ImageSize.Display.size.width())
|
||||||
self.leaveEvent(None)
|
self.leaveEvent(None)
|
||||||
self.info_label.setAutoFillBackground(False)
|
|
||||||
self.info_label.setObjectName("info_label")
|
self.info_label.setObjectName("info_label")
|
||||||
self.layout.addWidget(self.info_label)
|
layout.addWidget(self.info_label)
|
||||||
|
|
||||||
if self.igame and self.igame.needs_verification:
|
if self.igame and self.igame.needs_verification:
|
||||||
self.info_label.setText(self.texts["needs_verification"])
|
self.info_label.setText(self.texts["needs_verification"])
|
||||||
|
|
||||||
self.setLayout(self.layout)
|
self.setLayout(layout)
|
||||||
self.setFixedWidth(self.sizeHint().width())
|
|
||||||
|
|
||||||
self.game_utils.game_launched.connect(self.game_started)
|
self.game_utils.game_launched.connect(self.game_started)
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ class InstalledIconWidget(BaseInstalledWidget):
|
||||||
elif self.igame and self.igame.needs_verification:
|
elif self.igame and self.igame.needs_verification:
|
||||||
self.info_label.setText(self.texts["needs_verification"])
|
self.info_label.setText(self.texts["needs_verification"])
|
||||||
else:
|
else:
|
||||||
self.info_label.setText("")
|
self.info_label.setText(" ") # invisible text, cheap way to always vertical have size in label
|
||||||
|
|
||||||
def mousePressEvent(self, e: QMouseEvent):
|
def mousePressEvent(self, e: QMouseEvent):
|
||||||
# left button
|
# left button
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
from PyQt5.QtCore import Qt, QRect
|
from PyQt5.QtCore import Qt, QRect
|
||||||
from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor
|
from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor
|
||||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget
|
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget
|
||||||
|
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton
|
from rare.shared import LegendaryCoreSingleton
|
||||||
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
from rare.utils.utils import (
|
from rare.utils.utils import (
|
||||||
get_pixmap,
|
|
||||||
optimal_text_background,
|
optimal_text_background,
|
||||||
text_color_for_background,
|
text_color_for_background,
|
||||||
get_uninstalled_pixmap,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,11 +23,9 @@ class InstallingGameWidget(QWidget):
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
|
|
||||||
self.pixmap = QPixmap()
|
self.pixmap = QPixmap()
|
||||||
w = 200
|
|
||||||
# self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation)
|
|
||||||
self.image_widget = PaintWidget()
|
self.image_widget = PaintWidget()
|
||||||
self.setContentsMargins(4, 4, 4, 4)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.image_widget.setFixedSize(w, int(w * 4 / 3))
|
self.image_widget.setFixedSize(ImageSize.Display.size)
|
||||||
self.layout().addWidget(self.image_widget)
|
self.layout().addWidget(self.image_widget)
|
||||||
|
|
||||||
self.title_label = QLabel(f"<h4>Error</h4>")
|
self.title_label = QLabel(f"<h4>Error</h4>")
|
||||||
|
@ -62,18 +59,18 @@ class PaintWidget(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PaintWidget, self).__init__()
|
super(PaintWidget, self).__init__()
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
|
||||||
def set_game(self, app_name: str):
|
def set_game(self, app_name: str):
|
||||||
game = self.core.get_game(app_name, False)
|
game = self.core.get_game(app_name, False)
|
||||||
self.color_image = get_pixmap(game.app_name)
|
self.color_image = self.image_manager.get_pixmap(game.app_name, color=False)
|
||||||
w = 200
|
|
||||||
self.color_image = self.color_image.scaled(
|
self.color_image = self.color_image.scaled(
|
||||||
w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation
|
ImageSize.Display.size, transformMode=Qt.SmoothTransformation
|
||||||
)
|
)
|
||||||
self.setFixedSize(self.color_image.size())
|
self.setFixedSize(ImageSize.Display.size)
|
||||||
self.bw_image = get_uninstalled_pixmap(app_name)
|
self.bw_image = self.image_manager.get_pixmap(app_name, color=False)
|
||||||
self.bw_image = self.bw_image.scaled(
|
self.bw_image = self.bw_image.scaled(
|
||||||
w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation
|
ImageSize.Display.size, transformMode=Qt.SmoothTransformation
|
||||||
)
|
)
|
||||||
self.progress = 0
|
self.progress = 0
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QHBoxLayout
|
||||||
from legendary.core import LegendaryCore
|
from legendary.core import LegendaryCore
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
|
|
||||||
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import (
|
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import (
|
||||||
BaseUninstalledWidget,
|
BaseUninstalledWidget,
|
||||||
)
|
)
|
||||||
|
from rare.shared.image_manager import ImageSize
|
||||||
|
from rare.widgets.elide_label import ElideLabel
|
||||||
|
|
||||||
logger = getLogger("Uninstalled")
|
logger = getLogger("Uninstalled")
|
||||||
|
|
||||||
|
@ -14,19 +17,31 @@ logger = getLogger("Uninstalled")
|
||||||
class IconWidgetUninstalled(BaseUninstalledWidget):
|
class IconWidgetUninstalled(BaseUninstalledWidget):
|
||||||
def __init__(self, game: Game, core: LegendaryCore, pixmap):
|
def __init__(self, game: Game, core: LegendaryCore, pixmap):
|
||||||
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
|
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
|
||||||
self.layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.setObjectName("game_widget_icon")
|
self.setObjectName("game_widget_icon")
|
||||||
self.layout.addWidget(self.image)
|
layout.addWidget(self.image)
|
||||||
|
|
||||||
self.title_label = QLabel(f"<h3>{game.app_title}</h3>")
|
miniwidget = QWidget(self)
|
||||||
self.title_label.setWordWrap(True)
|
miniwidget.setFixedWidth(ImageSize.Display.size.width())
|
||||||
self.layout.addWidget(self.title_label)
|
minilayout = QHBoxLayout()
|
||||||
|
minilayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
minilayout.setSpacing(0)
|
||||||
|
miniwidget.setLayout(minilayout)
|
||||||
|
|
||||||
self.info_label = QLabel("")
|
self.title_label = ElideLabel(f"<h4>{game.app_title}</h4>", parent=miniwidget)
|
||||||
self.layout.addWidget(self.info_label)
|
self.title_label.setObjectName("game_widget")
|
||||||
|
minilayout.addWidget(self.title_label, stretch=2)
|
||||||
|
|
||||||
self.setLayout(self.layout)
|
minilayout.setAlignment(Qt.AlignTop)
|
||||||
self.setFixedWidth(self.sizeHint().width())
|
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:
|
def mousePressEvent(self, e) -> None:
|
||||||
# left button
|
# left button
|
||||||
|
@ -47,4 +62,4 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
|
||||||
if self.installing:
|
if self.installing:
|
||||||
self.info_label.setText("Installation...")
|
self.info_label.setText("Installation...")
|
||||||
else:
|
else:
|
||||||
self.info_label.setText("")
|
self.info_label.setText(" ") # invisible text, cheap way to always have vertical size in label
|
||||||
|
|
3
rare/models/__init__.py
Normal file
3
rare/models/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Models and data structures module for Rare
|
||||||
|
"""
|
22
rare/models/apiresults.py
Normal file
22
rare/models/apiresults.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, List, Dict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ApiResults:
|
||||||
|
game_list: Optional[List] = None
|
||||||
|
dlcs: Optional[Dict] = None
|
||||||
|
bit32_games: Optional[List] = None
|
||||||
|
mac_games: Optional[List] = None
|
||||||
|
no_asset_games: Optional[List] = None
|
||||||
|
saves: Optional[List] = None
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return (
|
||||||
|
self.game_list is not None
|
||||||
|
and self.dlcs is not None
|
||||||
|
and self.bit32_games is not None
|
||||||
|
and self.mac_games is not None
|
||||||
|
and self.no_asset_games is not None
|
||||||
|
and self.saves is not None
|
||||||
|
)
|
29
rare/models/signals.py
Normal file
29
rare/models/signals.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
|
|
||||||
|
from rare.utils.models import InstallOptionsModel
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalSignals(QObject):
|
||||||
|
exit_app = pyqtSignal(int) # exit code
|
||||||
|
send_notification = pyqtSignal(str) # app_title
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
install_game = pyqtSignal(InstallOptionsModel)
|
||||||
|
installation_finished = pyqtSignal(bool, str)
|
||||||
|
|
||||||
|
overlay_installation_finished = pyqtSignal()
|
||||||
|
|
||||||
|
update_gamelist = pyqtSignal(list)
|
||||||
|
game_uninstalled = pyqtSignal(str) # appname
|
||||||
|
|
||||||
|
set_discord_rpc = pyqtSignal(str) # app_name of running game
|
||||||
|
rpc_settings_updated = pyqtSignal()
|
||||||
|
|
||||||
|
wine_prefix_updated = pyqtSignal()
|
BIN
rare/resources/images/cover.png
Normal file
BIN
rare/resources/images/cover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 307 KiB |
|
@ -1,9 +1,17 @@
|
||||||
|
"""
|
||||||
|
Shared controller resources module
|
||||||
|
|
||||||
|
Each of the objects in this module should be instantiated ONCE
|
||||||
|
and only ONCE!
|
||||||
|
"""
|
||||||
|
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from legendary.core import LegendaryCore
|
from legendary.core import LegendaryCore
|
||||||
|
|
||||||
from rare.utils.models import ApiResults, GlobalSignals
|
from rare.models.apiresults import ApiResults
|
||||||
|
from rare.models.signals import GlobalSignals
|
||||||
|
|
||||||
_legendary_core_singleton: Optional[LegendaryCore] = None
|
_legendary_core_singleton: Optional[LegendaryCore] = None
|
||||||
_global_signals_singleton: Optional[GlobalSignals] = None
|
_global_signals_singleton: Optional[GlobalSignals] = None
|
367
rare/shared/image_manager.py
Normal file
367
rare/shared/image_manager.py
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import pickle
|
||||||
|
import zlib
|
||||||
|
from logging import getLogger
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import Tuple, Dict, Union, Type, List, Callable, Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from PyQt5.QtCore import (
|
||||||
|
Qt,
|
||||||
|
pyqtSignal,
|
||||||
|
QObject,
|
||||||
|
QSize,
|
||||||
|
QThreadPool,
|
||||||
|
QRunnable,
|
||||||
|
)
|
||||||
|
from PyQt5.QtGui import (
|
||||||
|
QPixmap,
|
||||||
|
QImage,
|
||||||
|
QPainter,
|
||||||
|
)
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
from legendary.models.game import Game
|
||||||
|
|
||||||
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||||
|
from rare.utils.paths import image_dir, resources_path
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger = getLogger("ImageManager")
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSize:
|
||||||
|
class Preset:
|
||||||
|
__img_factor = 67
|
||||||
|
__size: QSize
|
||||||
|
__divisor: float = 1.0
|
||||||
|
__pixel_ratio: float = 1.0
|
||||||
|
# lk: for prettier images set this to true
|
||||||
|
__smooth_transform: bool = False
|
||||||
|
|
||||||
|
def __init__(self, divisor: float, pixel_ratio: float):
|
||||||
|
self.__pixel_ratio = pixel_ratio
|
||||||
|
self.__divisor = divisor
|
||||||
|
self.__size = QSize(self.__img_factor * 3, self.__img_factor * 4) * pixel_ratio / divisor
|
||||||
|
if divisor > 2:
|
||||||
|
self.__smooth_transform = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self) -> QSize:
|
||||||
|
return self.__size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def divisor(self) -> float:
|
||||||
|
return self.__divisor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def smooth(self) -> bool:
|
||||||
|
return self.__smooth_transform
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pixel_ratio(self) -> float:
|
||||||
|
return self.__pixel_ratio
|
||||||
|
|
||||||
|
Image = Preset(1, 2)
|
||||||
|
"""! @brief Size and pixel ratio of the image on disk"""
|
||||||
|
|
||||||
|
Display = Preset(1, 1)
|
||||||
|
"""! @brief Size and pixel ratio for displaying"""
|
||||||
|
|
||||||
|
Normal = Display
|
||||||
|
"""! @brief Same as Display"""
|
||||||
|
|
||||||
|
Small = Preset(3, 1)
|
||||||
|
"""! @brief Small image size for displaying"""
|
||||||
|
|
||||||
|
Smaller = Preset(4, 1)
|
||||||
|
"""! @brief Smaller image size for displaying"""
|
||||||
|
|
||||||
|
Icon = Preset(5, 1)
|
||||||
|
"""! @brief Smaller image size for UI icons"""
|
||||||
|
|
||||||
|
|
||||||
|
class ImageManager(QObject):
|
||||||
|
class Worker(QRunnable):
|
||||||
|
class Signals(QObject):
|
||||||
|
# str: app_name
|
||||||
|
completed = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, func: Callable, updates: List, json_data: Dict, game: Game):
|
||||||
|
super(ImageManager.Worker, self).__init__()
|
||||||
|
self.signals = ImageManager.Worker.Signals()
|
||||||
|
self.setAutoDelete(True)
|
||||||
|
self.func = func
|
||||||
|
self.updates = updates
|
||||||
|
self.json_data = json_data
|
||||||
|
self.game = game
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.func(self.updates, self.json_data, self.game)
|
||||||
|
logger.debug(f" Emitting singal for game {self.game.app_name} - {self.game.app_title}")
|
||||||
|
self.signals.completed.emit(self.game.app_name)
|
||||||
|
|
||||||
|
# lk: the ordering in __img_types matters for the order of fallbacks
|
||||||
|
__img_types: List = ["DieselGameBoxTall", "Thumbnail", "DieselGameBoxLogo"]
|
||||||
|
__dl_retries = 1
|
||||||
|
__worker_app_names: List[str] = list()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(QObject, self).__init__()
|
||||||
|
self.core = LegendaryCoreSingleton()
|
||||||
|
self.signals = GlobalSignalsSingleton()
|
||||||
|
|
||||||
|
self.image_dir = Path(image_dir)
|
||||||
|
if not self.image_dir.is_dir():
|
||||||
|
self.image_dir.mkdir()
|
||||||
|
logger.info(f"Created image directory at {self.image_dir}")
|
||||||
|
|
||||||
|
self.device = ImageSize.Preset(1, QApplication.instance().devicePixelRatio())
|
||||||
|
|
||||||
|
self.threadpool = QThreadPool()
|
||||||
|
self.threadpool.setMaxThreadCount(8)
|
||||||
|
|
||||||
|
def __img_dir(self, app_name: str) -> Path:
|
||||||
|
return self.image_dir.joinpath(app_name)
|
||||||
|
|
||||||
|
def __img_json(self, app_name: str) -> Path:
|
||||||
|
return self.__img_dir(app_name).joinpath("image.json")
|
||||||
|
|
||||||
|
def __img_cache(self, app_name: str) -> Path:
|
||||||
|
return self.__img_dir(app_name).joinpath("image.cache")
|
||||||
|
|
||||||
|
def __img_color(self, app_name: str) -> Path:
|
||||||
|
return self.__img_dir(app_name).joinpath("installed.png")
|
||||||
|
|
||||||
|
def __img_gray(self, app_name: str) -> Path:
|
||||||
|
return self.__img_dir(app_name).joinpath("uninstalled.png")
|
||||||
|
|
||||||
|
def __prepare_download(self, game: Game, force: bool = False) -> Tuple[List, Dict]:
|
||||||
|
if force and self.__img_dir(game.app_name).exists():
|
||||||
|
self.__img_color(game.app_name).unlink(missing_ok=True)
|
||||||
|
self.__img_color(game.app_name).unlink(missing_ok=True)
|
||||||
|
if not self.__img_dir(game.app_name).is_dir():
|
||||||
|
self.__img_dir(game.app_name).mkdir()
|
||||||
|
|
||||||
|
# Load image checksums
|
||||||
|
if not self.__img_json(game.app_name).is_file():
|
||||||
|
json_data: Dict = dict(zip(self.__img_types, [None] * len(self.__img_types)))
|
||||||
|
else:
|
||||||
|
json_data = json.load(open(self.__img_json(game.app_name), "r"))
|
||||||
|
|
||||||
|
# lk: fast path for games without images, convert Rare's logo
|
||||||
|
if not game.metadata["keyImages"]:
|
||||||
|
if not self.__img_color(game.app_name).is_file() or not self.__img_gray(game.app_name).is_file():
|
||||||
|
cache_data: Dict = dict(zip(self.__img_types, [None] * len(self.__img_types)))
|
||||||
|
cache_data["DieselGameBoxTall"] = open(
|
||||||
|
resources_path.joinpath("images", "cover.png"), "rb"
|
||||||
|
).read()
|
||||||
|
# cache_data["DieselGameBoxLogo"] = open(
|
||||||
|
# resources_path.joinpath("images", "Rare_nonsquared.png"), "rb").read()
|
||||||
|
self.__convert(game, cache_data)
|
||||||
|
json_data["cache"] = None
|
||||||
|
json_data["scale"] = ImageSize.Image.pixel_ratio
|
||||||
|
json_data["size"] = ImageSize.Image.size.__str__()
|
||||||
|
json.dump(json_data, open(self.__img_json(game.app_name), "w"))
|
||||||
|
|
||||||
|
# lk: Find updates or initialize if images are missing.
|
||||||
|
# lk: `updates` will be empty for games without images
|
||||||
|
# lk: so everything below it is skipped
|
||||||
|
if not self.__img_color(game.app_name).is_file() or not self.__img_gray(game.app_name).is_file():
|
||||||
|
updates = [image for image in game.metadata["keyImages"] if image["type"] in self.__img_types]
|
||||||
|
else:
|
||||||
|
updates = list()
|
||||||
|
for image in game.metadata["keyImages"]:
|
||||||
|
if image["type"] in self.__img_types:
|
||||||
|
if json_data[image["type"]] != image["md5"]:
|
||||||
|
updates.append(image)
|
||||||
|
|
||||||
|
return updates, json_data
|
||||||
|
|
||||||
|
def __download(self, updates, json_data, game) -> bool:
|
||||||
|
# Decompress existing image.cache
|
||||||
|
if not self.__img_cache(game.app_name).is_file():
|
||||||
|
cache_data = dict(zip(self.__img_types, [None] * len(self.__img_types)))
|
||||||
|
else:
|
||||||
|
cache_data = self.__decompress(game)
|
||||||
|
|
||||||
|
# lk: filter updates again against the cache now that it is available
|
||||||
|
updates = [
|
||||||
|
image
|
||||||
|
for image in updates
|
||||||
|
if cache_data[image["type"]] is None or json_data[image["type"]] != image["md5"]
|
||||||
|
]
|
||||||
|
|
||||||
|
for image in updates:
|
||||||
|
logger.info(f"Downloading {image['type']} for {game.app_title}")
|
||||||
|
json_data[image["type"]] = image["md5"]
|
||||||
|
payload = {"resize": 1, "w": ImageSize.Image.size.width(), "h": ImageSize.Image.size.height()}
|
||||||
|
cache_data[image["type"]] = requests.get(image["url"], params=payload).content
|
||||||
|
|
||||||
|
self.__convert(game, cache_data)
|
||||||
|
# lk: don't keep the cache if there is no logo (kept for me)
|
||||||
|
# if cache_data["DieselGameBoxLogo"] is not None:
|
||||||
|
# self.__compress(game, cache_data)
|
||||||
|
self.__compress(game, cache_data)
|
||||||
|
|
||||||
|
# hash image cache
|
||||||
|
try:
|
||||||
|
with open(self.__img_cache(game.app_name), "rb") as archive:
|
||||||
|
archive_hash = hashlib.md5(archive.read()).hexdigest()
|
||||||
|
except FileNotFoundError:
|
||||||
|
archive_hash = None
|
||||||
|
|
||||||
|
json_data["cache"] = archive_hash
|
||||||
|
json_data["scale"] = ImageSize.Image.pixel_ratio
|
||||||
|
json_data["size"] = {"w": ImageSize.Image.size.width(), "h": ImageSize.Image.size.height()}
|
||||||
|
|
||||||
|
# write image.json
|
||||||
|
with open(self.__img_json(game.app_name), "w") as file:
|
||||||
|
json.dump(json_data, file)
|
||||||
|
|
||||||
|
return bool(updates)
|
||||||
|
|
||||||
|
def __convert(self, game, images, force=False) -> None:
|
||||||
|
for image in [self.__img_color(game.app_name), self.__img_gray(game.app_name)]:
|
||||||
|
if force and image.exists():
|
||||||
|
image.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
cover_data = None
|
||||||
|
for image_type in self.__img_types:
|
||||||
|
if images[image_type] is not None:
|
||||||
|
cover_data = images[image_type]
|
||||||
|
break
|
||||||
|
|
||||||
|
cover = QImage()
|
||||||
|
cover.loadFromData(cover_data)
|
||||||
|
cover.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||||
|
# lk: Images are not always 4/3, crop them to size
|
||||||
|
factor = min(cover.width() // 3, cover.height() // 4)
|
||||||
|
rem_w = (cover.width() - factor * 3) // 2
|
||||||
|
rem_h = (cover.height() - factor * 4) // 2
|
||||||
|
cover = cover.copy(rem_w, rem_h, factor * 3, factor * 4)
|
||||||
|
|
||||||
|
if images["DieselGameBoxLogo"] is not None:
|
||||||
|
logo = QImage()
|
||||||
|
logo.loadFromData(images["DieselGameBoxLogo"])
|
||||||
|
logo.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||||
|
if logo.width() > cover.width():
|
||||||
|
logo = logo.scaled(cover.width(), cover.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||||
|
painter = QPainter(cover)
|
||||||
|
painter.drawImage((cover.width() - logo.width()) // 2, cover.height() - logo.height(), logo)
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
cover = cover.scaled(ImageSize.Image.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||||
|
|
||||||
|
# this is not required if we ever want to re-apply the alpha channel
|
||||||
|
# cover = cover.convertToFormat(QImage.Format_Indexed8)
|
||||||
|
|
||||||
|
# add the alpha channel back to the cover
|
||||||
|
cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||||
|
|
||||||
|
cover.save(
|
||||||
|
str(self.__img_color(game.app_name)),
|
||||||
|
format="PNG",
|
||||||
|
)
|
||||||
|
# quick way to convert to grayscale
|
||||||
|
cover = cover.convertToFormat(QImage.Format_Grayscale8)
|
||||||
|
# add the alpha channel back to the grayscale cover
|
||||||
|
cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||||
|
cover.save(
|
||||||
|
str(self.__img_gray(game.app_name)),
|
||||||
|
format="PNG",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __compress(self, game: Game, data: Dict) -> None:
|
||||||
|
archive = open(self.__img_cache(game.app_name), "wb")
|
||||||
|
cdata = zlib.compress(pickle.dumps(data), level=-1)
|
||||||
|
archive.write(cdata)
|
||||||
|
archive.close()
|
||||||
|
|
||||||
|
def __decompress(self, game: Game) -> Dict:
|
||||||
|
archive = open(self.__img_cache(game.app_name), "rb")
|
||||||
|
data = zlib.decompress(archive.read())
|
||||||
|
archive.close()
|
||||||
|
data = pickle.loads(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def download_image(
|
||||||
|
self, game: Game, load_callback: Callable[[], None], priority: int, force: bool = False
|
||||||
|
) -> None:
|
||||||
|
updates, json_data = self.__prepare_download(game, force)
|
||||||
|
if not updates:
|
||||||
|
load_callback()
|
||||||
|
return
|
||||||
|
if updates and game.app_name not in self.__worker_app_names:
|
||||||
|
image_worker = ImageManager.Worker(self.__download, updates, json_data, game)
|
||||||
|
self.__worker_app_names.append(game.app_name)
|
||||||
|
|
||||||
|
image_worker.signals.completed.connect(load_callback)
|
||||||
|
image_worker.signals.completed.connect(lambda app_name: self.__worker_app_names.remove(app_name))
|
||||||
|
self.threadpool.start(image_worker, priority)
|
||||||
|
|
||||||
|
def download_image_blocking(self, game: Game, force: bool = False) -> None:
|
||||||
|
updates, json_data = self.__prepare_download(game, force)
|
||||||
|
if not updates:
|
||||||
|
return
|
||||||
|
if updates:
|
||||||
|
self.__download(updates, json_data, game)
|
||||||
|
|
||||||
|
def __get_cover(
|
||||||
|
self, container: Union[Type[QPixmap], Type[QImage]], app_name: str, color: bool = True
|
||||||
|
) -> Union[QPixmap, QImage]:
|
||||||
|
ret = container()
|
||||||
|
if not app_name:
|
||||||
|
raise RuntimeError("app_name is an empty string")
|
||||||
|
if color:
|
||||||
|
if self.__img_color(app_name).is_file():
|
||||||
|
ret.load(str(self.__img_color(app_name)))
|
||||||
|
else:
|
||||||
|
if self.__img_gray(app_name).is_file():
|
||||||
|
ret.load(str(self.__img_gray(app_name)))
|
||||||
|
if not ret.isNull():
|
||||||
|
ret.setDevicePixelRatio(ImageSize.Image.pixel_ratio)
|
||||||
|
# lk: Scaling happens at painting. It might be inefficient so leave this here as an alternative
|
||||||
|
# lk: If this is uncommented, the transformation in ImageWidget should be adjusted also
|
||||||
|
ret = ret.scaled(self.device.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||||
|
ret.setDevicePixelRatio(self.device.pixel_ratio)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_pixmap(self, app_name: str, color: bool = True) -> QPixmap:
|
||||||
|
"""
|
||||||
|
Use when the image is to be presented directly on the screen.
|
||||||
|
|
||||||
|
@param app_name: The RareGame object for this game
|
||||||
|
@param color: True to load the colored pixmap, False to load the grayscale
|
||||||
|
@return: QPixmap
|
||||||
|
"""
|
||||||
|
pixmap: QPixmap = self.__get_cover(QPixmap, app_name, color)
|
||||||
|
return pixmap
|
||||||
|
|
||||||
|
def get_image(self, app_name: str, color: bool = True) -> QImage:
|
||||||
|
"""
|
||||||
|
Use when the image has to be manipulated before being rendered.
|
||||||
|
|
||||||
|
@param app_name: The RareGame object for this game
|
||||||
|
@param color: True to load the colored image, False to load the grayscale
|
||||||
|
@return: QImage
|
||||||
|
"""
|
||||||
|
image: QImage = self.__get_cover(QImage, app_name, color)
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
_image_manager_singleton: Optional[ImageManager] = None
|
||||||
|
|
||||||
|
|
||||||
|
def ImageManagerSingleton(init: bool = False) -> ImageManager:
|
||||||
|
global _image_manager_singleton
|
||||||
|
if _image_manager_singleton is None and not init:
|
||||||
|
raise RuntimeError("Uninitialized use of ImageManagerSingleton")
|
||||||
|
if _image_manager_singleton is None:
|
||||||
|
_image_manager_singleton = ImageManager()
|
||||||
|
return _image_manager_singleton
|
|
@ -4,8 +4,6 @@ from dataclasses import field, dataclass
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
from typing import Union, List, Optional
|
from typing import Union, List, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
|
||||||
|
|
||||||
from legendary.core import LegendaryCore
|
from legendary.core import LegendaryCore
|
||||||
from legendary.downloader.mp.manager import DLManager
|
from legendary.downloader.mp.manager import DLManager
|
||||||
from legendary.models.downloading import AnalysisResult, ConditionCheckResult
|
from legendary.models.downloading import AnalysisResult, ConditionCheckResult
|
||||||
|
@ -103,49 +101,3 @@ class PathSpec:
|
||||||
return prefixes[0]
|
return prefixes[0]
|
||||||
else:
|
else:
|
||||||
return prefixes[:results]
|
return prefixes[:results]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ApiResults:
|
|
||||||
game_list: Optional[list] = None
|
|
||||||
dlcs: Optional[dict] = None
|
|
||||||
bit32_games: Optional[list] = None
|
|
||||||
mac_games: Optional[list] = None
|
|
||||||
no_asset_games: Optional[list] = None
|
|
||||||
saves: Optional[list] = None
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return (
|
|
||||||
self.game_list is not None
|
|
||||||
and self.dlcs is not None
|
|
||||||
and self.bit32_games is not None
|
|
||||||
and self.mac_games is not None
|
|
||||||
and self.no_asset_games is not None
|
|
||||||
and self.saves is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalSignals(QObject):
|
|
||||||
exit_app = pyqtSignal(int) # exit code
|
|
||||||
send_notification = pyqtSignal(str) # app_title
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
install_game = pyqtSignal(InstallOptionsModel)
|
|
||||||
installation_finished = pyqtSignal(bool, str)
|
|
||||||
|
|
||||||
overlay_installation_finished = pyqtSignal()
|
|
||||||
|
|
||||||
update_gamelist = pyqtSignal(list)
|
|
||||||
game_uninstalled = pyqtSignal(str) # appname
|
|
||||||
|
|
||||||
set_discord_rpc = pyqtSignal(str) # app_name of running game
|
|
||||||
rpc_settings_updated = pyqtSignal()
|
|
||||||
|
|
||||||
wine_prefix_updated = pyqtSignal()
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import json
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Tuple, List
|
from typing import List, Tuple
|
||||||
|
|
||||||
import qtawesome
|
import qtawesome
|
||||||
import requests
|
import requests
|
||||||
|
@ -18,11 +16,10 @@ from PyQt5.QtCore import (
|
||||||
QRunnable,
|
QRunnable,
|
||||||
QSettings,
|
QSettings,
|
||||||
QStandardPaths,
|
QStandardPaths,
|
||||||
Qt,
|
|
||||||
QFile,
|
QFile,
|
||||||
QDir,
|
QDir,
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import QPalette, QColor, QPixmap, QImage
|
from PyQt5.QtGui import QPalette, QColor, QImage
|
||||||
from PyQt5.QtWidgets import qApp, QStyleFactory
|
from PyQt5.QtWidgets import qApp, QStyleFactory
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
@ -45,85 +42,6 @@ from legendary.core import LegendaryCore
|
||||||
logger = getLogger("Utils")
|
logger = getLogger("Utils")
|
||||||
settings = QSettings("Rare", "Rare")
|
settings = QSettings("Rare", "Rare")
|
||||||
|
|
||||||
|
|
||||||
def download_images(progress: pyqtSignal, results: pyqtSignal, core: LegendaryCore):
|
|
||||||
if not os.path.isdir(image_dir):
|
|
||||||
os.makedirs(image_dir)
|
|
||||||
logger.info("Create Image dir")
|
|
||||||
|
|
||||||
# Download Images
|
|
||||||
games, dlcs = core.get_game_and_dlc_list(True, skip_ue=False)
|
|
||||||
results.emit((games, dlcs), "gamelist")
|
|
||||||
dlc_list = []
|
|
||||||
for i in dlcs.values():
|
|
||||||
dlc_list.append(i[0])
|
|
||||||
|
|
||||||
no_assets = core.get_non_asset_library_items()[0]
|
|
||||||
results.emit(no_assets, "no_assets")
|
|
||||||
|
|
||||||
game_list = games + dlc_list + no_assets
|
|
||||||
for i, game in enumerate(game_list):
|
|
||||||
if game.app_title == "Unreal Engine":
|
|
||||||
game.app_title += f" {game.app_name.split('_')[-1]}"
|
|
||||||
core.lgd.set_game_meta(game.app_name, game)
|
|
||||||
try:
|
|
||||||
download_image(game)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
shutil.rmtree(f"{image_dir}/{game.app_name}")
|
|
||||||
download_image(game)
|
|
||||||
progress.emit(i * 100 // len(game_list))
|
|
||||||
|
|
||||||
|
|
||||||
def download_image(game, force=False):
|
|
||||||
if force and os.path.exists(f"{image_dir}/{game.app_name}"):
|
|
||||||
shutil.rmtree(f"{image_dir}/{game.app_name}")
|
|
||||||
if not os.path.isdir(f"{image_dir}/{game.app_name}"):
|
|
||||||
os.mkdir(f"{image_dir}/{game.app_name}")
|
|
||||||
|
|
||||||
# to get picture updates
|
|
||||||
if not os.path.isfile(f"{image_dir}/{game.app_name}/image.json"):
|
|
||||||
json_data = {
|
|
||||||
"DieselGameBoxTall": None,
|
|
||||||
"DieselGameBoxLogo": None,
|
|
||||||
"Thumbnail": None,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
json_data = json.load(open(f"{image_dir}/{game.app_name}/image.json", "r"))
|
|
||||||
# Download
|
|
||||||
for image in game.metadata["keyImages"]:
|
|
||||||
if (
|
|
||||||
image["type"] == "DieselGameBoxTall"
|
|
||||||
or image["type"] == "DieselGameBoxLogo"
|
|
||||||
or image["type"] == "Thumbnail"
|
|
||||||
):
|
|
||||||
if image["type"] not in json_data.keys():
|
|
||||||
json_data[image["type"]] = None
|
|
||||||
if json_data[image["type"]] != image["md5"] or not os.path.isfile(
|
|
||||||
f"{image_dir}/{game.app_name}/{image['type']}.png"
|
|
||||||
):
|
|
||||||
# Download
|
|
||||||
json_data[image["type"]] = image["md5"]
|
|
||||||
# os.remove(f"{image_dir}/{game.app_name}/{image['type']}.png")
|
|
||||||
json.dump(
|
|
||||||
json_data, open(f"{image_dir}/{game.app_name}/image.json", "w")
|
|
||||||
)
|
|
||||||
logger.info(f"Download Image for Game: {game.app_title}")
|
|
||||||
url = image["url"]
|
|
||||||
resp = requests.get(url)
|
|
||||||
img = QImage()
|
|
||||||
img.loadFromData(resp.content)
|
|
||||||
img = img.scaled(
|
|
||||||
200,
|
|
||||||
200 * 4 // 3,
|
|
||||||
Qt.KeepAspectRatio,
|
|
||||||
transformMode=Qt.SmoothTransformation,
|
|
||||||
)
|
|
||||||
img.save(
|
|
||||||
os.path.join(image_dir, game.app_name, image["type"] + ".png"),
|
|
||||||
format="PNG",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
color_role_map = {
|
color_role_map = {
|
||||||
0: "WindowText",
|
0: "WindowText",
|
||||||
1: "Button",
|
1: "Button",
|
||||||
|
@ -272,16 +190,7 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link=
|
||||||
if not for_rare:
|
if not for_rare:
|
||||||
igame = core.get_installed_game(app_name)
|
igame = core.get_installed_game(app_name)
|
||||||
|
|
||||||
if os.path.exists(p := os.path.join(image_dir, igame.app_name, "Thumbnail.png")):
|
icon = os.path.join(os.path.join(image_dir, igame.app_name, "installed.png"))
|
||||||
icon = p
|
|
||||||
elif os.path.exists(
|
|
||||||
p := os.path.join(image_dir, igame.app_name, "DieselGameBoxLogo.png")
|
|
||||||
):
|
|
||||||
icon = p
|
|
||||||
else:
|
|
||||||
icon = os.path.join(
|
|
||||||
os.path.join(image_dir, igame.app_name, "DieselGameBoxTall.png")
|
|
||||||
)
|
|
||||||
icon = icon.replace(".png", "")
|
icon = icon.replace(".png", "")
|
||||||
|
|
||||||
if platform.system() == "Linux":
|
if platform.system() == "Linux":
|
||||||
|
@ -397,22 +306,6 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link=
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_pixmap(app_name: str) -> QPixmap:
|
|
||||||
for img in ["FinalArt.png", "DieselGameBoxTall.png", "DieselGameBoxLogo.png"]:
|
|
||||||
if os.path.exists(image := os.path.join(image_dir, app_name, img)):
|
|
||||||
pixmap = QPixmap(image)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
pixmap = QPixmap()
|
|
||||||
return pixmap
|
|
||||||
|
|
||||||
|
|
||||||
def get_uninstalled_pixmap(app_name: str) -> QPixmap:
|
|
||||||
pm = get_pixmap(app_name)
|
|
||||||
grey_image = pm.toImage().convertToFormat(QImage.Format_Grayscale8)
|
|
||||||
return QPixmap.fromImage(grey_image)
|
|
||||||
|
|
||||||
|
|
||||||
def optimal_text_background(image: list) -> Tuple[int, int, int]:
|
def optimal_text_background(image: list) -> Tuple[int, int, int]:
|
||||||
"""
|
"""
|
||||||
Finds an optimal background color for text on the image by calculating the
|
Finds an optimal background color for text on the image by calculating the
|
||||||
|
|
3
rare/widgets/__init__.py
Normal file
3
rare/widgets/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Reusable widgets module for Rare
|
||||||
|
"""
|
25
rare/widgets/elide_label.py
Normal file
25
rare/widgets/elide_label.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtGui import QFontMetrics, QPaintEvent
|
||||||
|
from PyQt5.QtWidgets import QLabel
|
||||||
|
|
||||||
|
|
||||||
|
class ElideLabel(QLabel):
|
||||||
|
__text: str = ""
|
||||||
|
|
||||||
|
def __init__(self, text="", parent=None, flags=Qt.WindowFlags()):
|
||||||
|
super(ElideLabel, self).__init__(parent=parent, flags=flags)
|
||||||
|
if text:
|
||||||
|
self.setText(text)
|
||||||
|
|
||||||
|
def setText(self, a0: str) -> None:
|
||||||
|
self.__text = a0
|
||||||
|
self.__setElideText(a0)
|
||||||
|
|
||||||
|
def __setElideText(self, a0: str):
|
||||||
|
metrics = QFontMetrics(self.font())
|
||||||
|
elided_text = metrics.elidedText(a0, Qt.ElideRight, self.width())
|
||||||
|
super(ElideLabel, self).setText(elided_text)
|
||||||
|
|
||||||
|
def paintEvent(self, a0: QPaintEvent) -> None:
|
||||||
|
self.__setElideText(self.__text)
|
||||||
|
super(ElideLabel, self).paintEvent(a0)
|
Loading…
Reference in a new issue