ImageManager: Add the ability to request different types of cover art images
This commit is contained in:
parent
4587c73671
commit
da93dc2e5e
|
@ -14,6 +14,7 @@ from rare.components.dialogs.install_dialog import InstallDialog
|
|||
from rare.components.dialogs.uninstall_dialog import UninstallDialog
|
||||
from rare.lgndr.models.downloading import UIUpdate
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.image import ImageSize
|
||||
from rare.models.install import InstallOptionsModel, InstallQueueItemModel, UninstallOptionsModel
|
||||
from rare.models.options import options
|
||||
from rare.shared import RareCore
|
||||
|
@ -209,9 +210,9 @@ class DownloadsTab(QWidget):
|
|||
self.__thread = dl_thread
|
||||
self.download_widget.ui.kill_button.setDisabled(False)
|
||||
self.download_widget.ui.dl_name.setText(item.download.game.app_title)
|
||||
self.download_widget.setPixmap(
|
||||
RareCore.instance().image_manager().get_pixmap(rgame.app_name, True)
|
||||
)
|
||||
self.download_widget.setPixmap(self.rcore.image_manager().get_pixmap(
|
||||
rgame.app_name, ImageSize.Wide, True
|
||||
))
|
||||
|
||||
self.signals.application.notify.emit(
|
||||
self.tr("Downloads"),
|
||||
|
|
|
@ -29,7 +29,7 @@ class DownloadWidget(ImageWidget):
|
|||
|
||||
def prepare_pixmap(self, pixmap: QPixmap) -> QPixmap:
|
||||
device: QImage = QImage(
|
||||
pixmap.size().width() * 3,
|
||||
pixmap.size().width() * 1,
|
||||
int(self.sizeHint().height() * pixmap.devicePixelRatioF()) + 1,
|
||||
QImage.Format_ARGB32_Premultiplied,
|
||||
)
|
||||
|
@ -38,9 +38,9 @@ class DownloadWidget(ImageWidget):
|
|||
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.02, Qt.transparent)
|
||||
gradient.setColorAt(0.5, Qt.black)
|
||||
gradient.setColorAt(0.85, Qt.transparent)
|
||||
gradient.setColorAt(0.98, Qt.transparent)
|
||||
painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
|
||||
painter.fillRect(device.rect(), gradient)
|
||||
painter.end()
|
||||
|
|
|
@ -34,7 +34,7 @@ class QueueInfoWidget(QWidget):
|
|||
self.image_manager = ImageManagerSingleton()
|
||||
|
||||
self.image = ImageWidget(self)
|
||||
self.image.setFixedSize(ImageSize.Smallest)
|
||||
self.image.setFixedSize(ImageSize.LibraryIcon)
|
||||
self.ui.image_layout.addWidget(self.image)
|
||||
|
||||
self.ui.queue_info_layout.setAlignment(Qt.AlignTop)
|
||||
|
@ -50,7 +50,7 @@ class QueueInfoWidget(QWidget):
|
|||
|
||||
if old_igame:
|
||||
self.ui.title.setText(old_igame.title)
|
||||
self.image.setPixmap(self.image_manager.get_pixmap(old_igame.app_name, color=True))
|
||||
self.image.setPixmap(self.image_manager.get_pixmap(old_igame.app_name, ImageSize.LibraryIcon))
|
||||
|
||||
def update_information(self, game, igame, analysis, old_igame):
|
||||
self.ui.title.setText(game.app_title)
|
||||
|
@ -60,7 +60,7 @@ class QueueInfoWidget(QWidget):
|
|||
self.ui.local_version.setText(elide_text(self.ui.local_version, igame.version))
|
||||
self.ui.dl_size.setText(format_size(analysis.dl_size) if analysis else "")
|
||||
self.ui.install_size.setText(format_size(analysis.install_size) if analysis else "")
|
||||
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, color=True))
|
||||
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, ImageSize.LibraryIcon))
|
||||
|
||||
|
||||
class UpdateWidget(QFrame):
|
||||
|
|
|
@ -58,7 +58,7 @@ class GameDetails(QWidget, SideTabContents):
|
|||
self.rgame: Optional[RareGame] = None
|
||||
|
||||
self.image = ImageWidget(self)
|
||||
self.image.setFixedSize(ImageSize.Display)
|
||||
self.image.setFixedSize(ImageSize.DisplayTall)
|
||||
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)
|
||||
|
||||
self.ui.install_button.clicked.connect(self.__on_install)
|
||||
|
@ -270,7 +270,7 @@ class GameDetails(QWidget, SideTabContents):
|
|||
@pyqtSlot()
|
||||
def __update_widget(self):
|
||||
""" React to state updates from RareGame """
|
||||
self.image.setPixmap(self.rgame.get_pixmap(True))
|
||||
self.image.setPixmap(self.rgame.get_pixmap(ImageSize.DisplayTall, True))
|
||||
|
||||
self.ui.lbl_version.setDisabled(self.rgame.is_non_asset)
|
||||
self.ui.version.setDisabled(self.rgame.is_non_asset)
|
||||
|
|
|
@ -23,28 +23,28 @@ class GameDlcWidget(QFrame):
|
|||
self.rdlc = rdlc
|
||||
|
||||
self.image = ImageWidget(self)
|
||||
self.image.setFixedSize(ImageSize.Smallest)
|
||||
self.image.setFixedSize(ImageSize.LibraryIcon)
|
||||
self.ui.dlc_layout.insertWidget(0, self.image)
|
||||
|
||||
self.ui.dlc_name.setText(rdlc.app_title)
|
||||
self.ui.version.setText(rdlc.version)
|
||||
self.ui.app_name.setText(rdlc.app_name)
|
||||
|
||||
self.image.setPixmap(rdlc.pixmap)
|
||||
|
||||
# self.image.setPixmap(rdlc.get_pixmap_icon(rdlc.is_installed))
|
||||
self.__update()
|
||||
|
||||
rdlc.signals.widget.update.connect(self.__update)
|
||||
|
||||
@pyqtSlot()
|
||||
def __update(self):
|
||||
self.ui.action_button.setEnabled(self.rdlc.is_idle)
|
||||
self.image.setPixmap(self.rdlc.pixmap)
|
||||
self.image.setPixmap(self.rdlc.get_pixmap(ImageSize.LibraryIcon, self.rdlc.is_installed))
|
||||
|
||||
def showEvent(self, a0: QShowEvent) -> None:
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
if self.rdlc.pixmap.isNull():
|
||||
self.rdlc.load_pixmap()
|
||||
if not self.rdlc.has_pixmap:
|
||||
self.rdlc.load_pixmaps()
|
||||
super().showEvent(a0)
|
||||
|
||||
|
||||
|
|
|
@ -58,18 +58,13 @@ class GameWidget(LibraryWidget):
|
|||
self.update_actions()
|
||||
|
||||
# signals
|
||||
self.rgame.signals.widget.update.connect(lambda: self.setPixmap(self.rgame.pixmap))
|
||||
self.rgame.signals.widget.update.connect(self.update_pixmap)
|
||||
self.rgame.signals.widget.update.connect(self.update_buttons)
|
||||
self.rgame.signals.widget.update.connect(self.update_state)
|
||||
self.rgame.signals.game.installed.connect(self.update_actions)
|
||||
self.rgame.signals.game.uninstalled.connect(self.update_actions)
|
||||
|
||||
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.start.connect(self.start_progress)
|
||||
self.rgame.signals.progress.update.connect(
|
||||
lambda p: self.updateProgress(p)
|
||||
)
|
||||
|
@ -104,10 +99,10 @@ class GameWidget(LibraryWidget):
|
|||
# lk: abstract class for typing, the `self.ui` attribute should be used
|
||||
# lk: by the Ui class in the children. It must contain at least the same
|
||||
# lk: attributes as `GameWidgetUi` class
|
||||
__slots__ = "ui"
|
||||
__slots__ = "ui", "update_pixmap", "start_progress"
|
||||
|
||||
def paintEvent(self, a0: QPaintEvent) -> None:
|
||||
if not self.visibleRegion().isNull() and self.rgame.pixmap.isNull():
|
||||
if not self.visibleRegion().isNull() and not self.rgame.has_pixmap:
|
||||
self.startTimer(random.randrange(42, 2361, 129), Qt.CoarseTimer)
|
||||
# self.startTimer(random.randrange(42, 2361, 363), Qt.VeryCoarseTimer)
|
||||
# self.rgame.load_pixmap()
|
||||
|
@ -115,7 +110,7 @@ class GameWidget(LibraryWidget):
|
|||
|
||||
def timerEvent(self, a0):
|
||||
self.killTimer(a0.timerId())
|
||||
self.rgame.load_pixmap()
|
||||
self.rgame.load_pixmaps()
|
||||
|
||||
def showEvent(self, a0: QShowEvent) -> None:
|
||||
if a0.spontaneous():
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from logging import getLogger
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QEvent
|
||||
from PyQt5.QtCore import QEvent, pyqtSlot
|
||||
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.image import ImageSize
|
||||
|
@ -15,7 +15,7 @@ class IconGameWidget(GameWidget):
|
|||
def __init__(self, rgame: RareGame, parent=None):
|
||||
super().__init__(rgame, parent)
|
||||
self.setObjectName(f"{rgame.app_name}")
|
||||
self.setFixedSize(ImageSize.Library)
|
||||
self.setFixedSize(ImageSize.LibraryTall)
|
||||
self.ui = IconWidget()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
|
@ -34,6 +34,17 @@ class IconGameWidget(GameWidget):
|
|||
self.ui.launch_btn.installEventFilter(self)
|
||||
self.ui.install_btn.installEventFilter(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def update_pixmap(self):
|
||||
self.setPixmap(self.rgame.get_pixmap(ImageSize.LibraryTall, self.rgame.is_installed))
|
||||
|
||||
@pyqtSlot()
|
||||
def start_progress(self):
|
||||
self.showProgress(
|
||||
self.rgame.get_pixmap(ImageSize.LibraryTall, True),
|
||||
self.rgame.get_pixmap(ImageSize.LibraryTall, False)
|
||||
)
|
||||
|
||||
def enterEvent(self, a0: Optional[QEvent] = None) -> None:
|
||||
if a0 is not None:
|
||||
a0.accept()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import Qt, QEvent, QRect
|
||||
from PyQt5.QtCore import Qt, QEvent, QRect, pyqtSlot
|
||||
from PyQt5.QtGui import (
|
||||
QPalette,
|
||||
QBrush,
|
||||
|
@ -12,6 +12,7 @@ from PyQt5.QtGui import (
|
|||
)
|
||||
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.image import ImageSize
|
||||
from rare.utils.misc import format_size
|
||||
from .game_widget import GameWidget
|
||||
from .list_widget import ListWidget
|
||||
|
@ -50,6 +51,17 @@ class ListGameWidget(GameWidget):
|
|||
self.ui.launch_btn.installEventFilter(self)
|
||||
self.ui.install_btn.installEventFilter(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def update_pixmap(self):
|
||||
self.setPixmap(self.rgame.get_pixmap(ImageSize.LibraryWide, self.rgame.is_installed))
|
||||
|
||||
@pyqtSlot()
|
||||
def start_progress(self):
|
||||
self.showProgress(
|
||||
self.rgame.get_pixmap(ImageSize.LibraryWide, True),
|
||||
self.rgame.get_pixmap(ImageSize.LibraryWide, False)
|
||||
)
|
||||
|
||||
def enterEvent(self, a0: QEvent = None) -> None:
|
||||
if a0 is not None:
|
||||
a0.accept()
|
||||
|
@ -70,7 +82,7 @@ class ListGameWidget(GameWidget):
|
|||
|
||||
def prepare_pixmap(self, pixmap: QPixmap) -> QPixmap:
|
||||
device: QImage = QImage(
|
||||
pixmap.size().width() * 3,
|
||||
pixmap.size().width() * 1,
|
||||
int(self.sizeHint().height() * pixmap.devicePixelRatioF()) + 1,
|
||||
QImage.Format_ARGB32_Premultiplied
|
||||
)
|
||||
|
@ -79,9 +91,9 @@ class ListGameWidget(GameWidget):
|
|||
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.02, Qt.transparent)
|
||||
gradient.setColorAt(0.5, Qt.black)
|
||||
gradient.setColorAt(0.85, Qt.transparent)
|
||||
gradient.setColorAt(0.98, Qt.transparent)
|
||||
painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
|
||||
painter.fillRect(device.rect(), gradient)
|
||||
painter.end()
|
||||
|
@ -104,7 +116,7 @@ class ListGameWidget(GameWidget):
|
|||
brush = QBrush(self._pixmap)
|
||||
brush.setTransform(self._transform)
|
||||
width = int(self._pixmap.width() / self._pixmap.devicePixelRatioF())
|
||||
origin = self.width() - width
|
||||
origin = self.width() // 2
|
||||
painter.setBrushOrigin(origin, 0)
|
||||
fill_rect = QRect(origin, 0, width, self.height())
|
||||
painter.fillRect(fill_rect, brush)
|
||||
|
|
|
@ -41,7 +41,7 @@ class StoreDetailsWidget(QWidget, SideTabContents):
|
|||
self.catalog_offer: CatalogOfferModel = None
|
||||
|
||||
self.image = LoadingImageWidget(store_api.cached_manager, self)
|
||||
self.image.setFixedSize(ImageSize.Display)
|
||||
self.image.setFixedSize(ImageSize.DisplayTall)
|
||||
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)
|
||||
self.ui.left_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class StoreItemWidget(ItemWidget):
|
|||
class ResultsItemWidget(ItemWidget):
|
||||
def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=None):
|
||||
super(ResultsItemWidget, self).__init__(manager, catalog_game, parent=parent)
|
||||
self.setFixedSize(ImageSize.Display)
|
||||
self.setFixedSize(ImageSize.DisplayTall)
|
||||
self.ui.setupUi(self)
|
||||
|
||||
key_images = catalog_game.keyImages
|
||||
|
|
|
@ -8,10 +8,11 @@ from threading import Lock
|
|||
from typing import List, Optional, Dict, Set
|
||||
|
||||
from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool
|
||||
from PyQt5.QtGui import QPixmap, QPixmapCache
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from legendary.lfs import eos
|
||||
from legendary.models.game import Game, InstalledGame
|
||||
|
||||
from rare.models.image import ImageSize
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.base_game import RareGameBase, RareGameSlim
|
||||
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
|
||||
|
@ -85,7 +86,7 @@ class RareGame(RareGameSlim):
|
|||
if self.game.app_title == "Unreal Engine":
|
||||
self.game.app_title += f" {self.game.app_name.split('_')[-1]}"
|
||||
|
||||
self.pixmap: QPixmap = QPixmap()
|
||||
self.has_pixmap: bool = False
|
||||
self.metadata: RareGame.Metadata = RareGame.Metadata()
|
||||
self.__load_metadata()
|
||||
self.grant_date()
|
||||
|
@ -444,11 +445,11 @@ class RareGame(RareGameSlim):
|
|||
if elapsed_time.days > 3:
|
||||
logger.info("Refreshing ProtonDB grade for %s", self.app_title)
|
||||
|
||||
def _set_steam_grade():
|
||||
def set_steam_grade():
|
||||
appid, rating = get_rating(self.core, self.app_name)
|
||||
self.set_steam_grade(appid, rating)
|
||||
|
||||
worker = QRunnable.create(_set_steam_grade)
|
||||
worker = QRunnable.create(set_steam_grade)
|
||||
QThreadPool.globalInstance().start(worker)
|
||||
self.metadata.steam_grade = "pending"
|
||||
return self.metadata.steam_grade
|
||||
|
@ -504,20 +505,19 @@ class RareGame(RareGameSlim):
|
|||
return bool(not self.is_foreign or self.can_run_offline)
|
||||
return False
|
||||
|
||||
def get_pixmap(self, color=True) -> QPixmap:
|
||||
QPixmapCache.clear()
|
||||
return self.image_manager.get_pixmap(self.app_name, color)
|
||||
def get_pixmap(self, preset: ImageSize.Preset, color=True) -> QPixmap:
|
||||
return self.image_manager.get_pixmap(self.app_name, preset, color)
|
||||
|
||||
@pyqtSlot(object)
|
||||
def set_pixmap(self):
|
||||
self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed)
|
||||
QPixmapCache.clear()
|
||||
if not self.pixmap.isNull():
|
||||
# self.pixmap = not self.image_manager.get_pixmap(self.app_name, self.is_installed).isNull()
|
||||
self.has_pixmap = True
|
||||
if self.has_pixmap:
|
||||
self.signals.widget.update.emit()
|
||||
|
||||
def load_pixmap(self):
|
||||
""" Do not call this function, call set_pixmap instead. This is only used for startup image loading """
|
||||
if self.pixmap.isNull():
|
||||
def load_pixmaps(self):
|
||||
""" Do not call this function, call set_pixmap instead. This is only used for initial image loading """
|
||||
if not self.has_pixmap:
|
||||
self.image_manager.download_image(self.game, self.set_pixmap, 0, False)
|
||||
|
||||
def refresh_pixmap(self):
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Tuple
|
|||
from PyQt5.QtCore import QSize
|
||||
|
||||
|
||||
class Orientation(Enum):
|
||||
class ImageType(Enum):
|
||||
Tall = 0
|
||||
Wide = 1
|
||||
Icon = 3
|
||||
|
@ -13,17 +13,17 @@ class Orientation(Enum):
|
|||
|
||||
class ImageSize:
|
||||
class Preset:
|
||||
def __init__(self, divisor: float, pixel_ratio: float, orientation: Orientation = Orientation.Tall,
|
||||
def __init__(self, divisor: float, pixel_ratio: float, orientation: ImageType = ImageType.Tall,
|
||||
base: 'ImageSize.Preset' = None):
|
||||
self.__divisor = divisor
|
||||
self.__pixel_ratio = pixel_ratio
|
||||
if orientation == Orientation.Tall:
|
||||
if orientation == ImageType.Tall:
|
||||
self.__img_factor = 67
|
||||
self.__size = QSize(self.__img_factor * 3, self.__img_factor * 4) * pixel_ratio / divisor
|
||||
if orientation == Orientation.Wide:
|
||||
if orientation == ImageType.Wide:
|
||||
self.__img_factor = 34
|
||||
self.__size = QSize(self.__img_factor * 16, self.__img_factor * 9) * pixel_ratio / divisor
|
||||
if orientation == Orientation.Icon:
|
||||
if orientation == ImageType.Icon:
|
||||
self.__img_factor = 128
|
||||
self.__size = QSize(self.__img_factor * 1, self.__img_factor * 1) * pixel_ratio / divisor
|
||||
self.__orientation = orientation
|
||||
|
@ -57,14 +57,14 @@ class ImageSize:
|
|||
return self.__pixel_ratio
|
||||
|
||||
@property
|
||||
def orientation(self) -> Orientation:
|
||||
def orientation(self) -> ImageType:
|
||||
return self.__orientation
|
||||
|
||||
@property
|
||||
def aspect_ratio(self) -> Tuple[int, int]:
|
||||
if self.__orientation == Orientation.Tall:
|
||||
if self.__orientation == ImageType.Tall:
|
||||
return 3, 4
|
||||
elif self.__orientation == Orientation.Wide:
|
||||
elif self.__orientation == ImageType.Wide:
|
||||
return 16, 9
|
||||
else:
|
||||
return 0, 0
|
||||
|
@ -73,34 +73,28 @@ class ImageSize:
|
|||
def base(self) -> 'ImageSize.Preset':
|
||||
return self.__base
|
||||
|
||||
Image = Preset(1, 1)
|
||||
Tall = Preset(1, 1)
|
||||
"""! @brief Size and pixel ratio of the image on disk"""
|
||||
|
||||
ImageWide = Preset(1, 1, Orientation.Wide)
|
||||
"""! @brief Size and pixel ratio for wide 16/9 image on disk"""
|
||||
|
||||
Display = Preset(1, 1, base=Image)
|
||||
DisplayTall = Preset(1, 1, base=Tall)
|
||||
"""! @brief Size and pixel ratio for displaying"""
|
||||
|
||||
DisplayWide = Preset(2, 1, Orientation.Wide, base=ImageWide)
|
||||
"""! @brief Size and pixel ratio for wide 16/9 image display"""
|
||||
|
||||
LibraryWide = Preset(2.41, 1, Orientation.Wide, base=ImageWide)
|
||||
|
||||
Library = Preset(1.21, 1, base=Image)
|
||||
LibraryTall = Preset(1.21, 1, base=Tall)
|
||||
"""! @brief Same as Display"""
|
||||
|
||||
Small = Preset(3, 1, base=Image)
|
||||
"""! @brief Small image size for displaying"""
|
||||
Wide = Preset(1, 1, ImageType.Wide)
|
||||
"""! @brief Size and pixel ratio for wide 16/9 image on disk"""
|
||||
|
||||
SmallWide = Preset(6, 1, Orientation.Wide, base=ImageWide)
|
||||
"""! @brief Small image size for displaying"""
|
||||
DisplayWide = Preset(2, 1, ImageType.Wide, base=Wide)
|
||||
"""! @brief Size and pixel ratio for wide 16/9 image display"""
|
||||
|
||||
Smaller = Preset(4, 1, base=Image)
|
||||
"""! @brief Smaller image size for displaying"""
|
||||
LibraryWide = Preset(2.41, 1, ImageType.Wide, base=Wide)
|
||||
|
||||
SmallerWide = Preset(8, 1, Orientation.Wide, base=ImageWide)
|
||||
"""! @brief Smaller image size for displaying"""
|
||||
Icon = Preset(1, 1, ImageType.Icon)
|
||||
"""! @brief Size and pixel ratio of the icon on disk"""
|
||||
|
||||
Smallest = Preset(5, 1, base=Image)
|
||||
"""! @brief Smaller image size for UI icons"""
|
||||
DisplayIcon = Preset(1, 1, ImageType.Icon, base=Icon)
|
||||
"""! @brief Size and pixel ratio of the icon on disk"""
|
||||
|
||||
LibraryIcon = Preset(2.2, 1, ImageType.Icon, base=Icon)
|
||||
"""! @brief Size and pixel ratio of the icon on disk"""
|
||||
|
|
|
@ -16,9 +16,18 @@ from PyQt5.QtWidgets import QApplication
|
|||
from legendary.models.game import Game
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.image import ImageSize
|
||||
from rare.models.image import ImageSize, ImageType
|
||||
from rare.models.signals import GlobalSignals
|
||||
from rare.utils.paths import image_dir, resources_path, desktop_icon_suffix
|
||||
from rare.utils.paths import (
|
||||
image_dir,
|
||||
image_dir_game,
|
||||
image_tall_path,
|
||||
image_wide_path,
|
||||
image_icon_path,
|
||||
resources_path,
|
||||
desktop_icon_suffix,
|
||||
desktop_icon_path,
|
||||
)
|
||||
|
||||
# from requests_futures.sessions import FuturesSession
|
||||
|
||||
|
@ -67,53 +76,35 @@ class ImageManager(QObject):
|
|||
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(6)
|
||||
|
||||
def __img_dir(self, app_name: str) -> Path:
|
||||
return self.image_dir.joinpath(app_name)
|
||||
@staticmethod
|
||||
def __img_json(app_name: str) -> Path:
|
||||
return image_dir_game(app_name).joinpath("image.json")
|
||||
|
||||
def __img_json(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("image.json")
|
||||
@staticmethod
|
||||
def __img_cache(app_name: str) -> Path:
|
||||
return image_dir_game(app_name).joinpath("image.cache")
|
||||
|
||||
def __img_cache(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("image.cache")
|
||||
|
||||
def __img_tall_color(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("tall.png")
|
||||
|
||||
def __img_tall_gray(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("tall_uninstalled.png")
|
||||
|
||||
def __img_wide_color(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("wide.png")
|
||||
|
||||
def __img_wide_gray(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("wide_uninstalled.png")
|
||||
|
||||
def __img_logo(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath("logo.png")
|
||||
|
||||
def __img_icon(self, app_name: str) -> Path:
|
||||
return self.__img_dir(app_name).joinpath(f"icon.{desktop_icon_suffix()}")
|
||||
|
||||
def __img_all(self, app_name: str) -> Tuple:
|
||||
@staticmethod
|
||||
def __img_all(app_name: str) -> Tuple:
|
||||
return (
|
||||
self.__img_icon(app_name),
|
||||
self.__img_tall_color(app_name),
|
||||
self.__img_tall_gray(app_name),
|
||||
self.__img_wide_color(app_name),
|
||||
self.__img_tall_gray(app_name),
|
||||
image_tall_path(app_name),
|
||||
image_tall_path(app_name, color=False),
|
||||
image_wide_path(app_name),
|
||||
image_wide_path(app_name, color=False),
|
||||
image_icon_path(app_name),
|
||||
image_icon_path(app_name, color=False),
|
||||
desktop_icon_path(app_name),
|
||||
)
|
||||
|
||||
def __prepare_download(self, game: Game, force: bool = False) -> Tuple[List, Dict]:
|
||||
if force and self.__img_dir(game.app_name).exists():
|
||||
if force and image_dir_game(game.app_name).exists():
|
||||
for file in self.__img_all(game.app_name):
|
||||
file.unlink(missing_ok=True)
|
||||
if not self.__img_dir(game.app_name).is_dir():
|
||||
self.__img_dir(game.app_name).mkdir()
|
||||
if not image_dir_game(game.app_name).is_dir():
|
||||
image_dir_game(game.app_name).mkdir()
|
||||
|
||||
# Load image checksums
|
||||
if not self.__img_json(game.app_name).is_file():
|
||||
|
@ -160,8 +151,8 @@ class ImageManager(QObject):
|
|||
# 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"] = {"w": ImageSize.Image.size.width(), "h": ImageSize.Image.size.height()}
|
||||
json_data["scale"] = ImageSize.Tall.pixel_ratio
|
||||
json_data["size"] = {"w": ImageSize.Tall.size.width(), "h": ImageSize.Tall.size.height()}
|
||||
json.dump(json_data, open(self.__img_json(game.app_name), "w"))
|
||||
else:
|
||||
updates = [image for image in candidates if image["type"] in self.__img_types]
|
||||
|
@ -214,12 +205,12 @@ class ImageManager(QObject):
|
|||
logger.info(f"Downloading {image['type']} for {game.app_name} ({game.app_title})")
|
||||
json_data[image["type"]] = image["md5"]
|
||||
if image["type"] in self.__img_tall_types:
|
||||
payload = {"resize": 1, "w": ImageSize.Image.size.width(), "h": ImageSize.Image.size.height()}
|
||||
payload = {"resize": 1, "w": ImageSize.Tall.size.width(), "h": ImageSize.Tall.size.height()}
|
||||
elif image["type"] in self.__img_wide_types:
|
||||
payload = {"resize": 1, "w": ImageSize.ImageWide.size.width(), "h": ImageSize.ImageWide.size.height()}
|
||||
payload = {"resize": 1, "w": ImageSize.Wide.size.width(), "h": ImageSize.Wide.size.height()}
|
||||
else:
|
||||
# Set the larger of the sizes for everything else
|
||||
payload = {"resize": 1, "w": ImageSize.ImageWide.size.width(), "h": ImageSize.ImageWide.size.height()}
|
||||
payload = {"resize": 1, "w": ImageSize.Wide.size.width(), "h": ImageSize.Wide.size.height()}
|
||||
try:
|
||||
# cache_data[image["type"]] = requests.get(image["url"], params=payload).content
|
||||
cache_data[image["type"]] = requests.get(image["url"], params=payload, timeout=10).content
|
||||
|
@ -241,8 +232,8 @@ class ImageManager(QObject):
|
|||
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()}
|
||||
json_data["scale"] = ImageSize.Tall.pixel_ratio
|
||||
json_data["size"] = {"w": ImageSize.Tall.size.width(), "h": ImageSize.Tall.size.height()}
|
||||
|
||||
# write image.json
|
||||
with open(self.__img_json(game.app_name), "w") as file:
|
||||
|
@ -332,11 +323,11 @@ class ImageManager(QObject):
|
|||
|
||||
return image.scaled(preset.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
|
||||
tall = convert_image(tall_data, logo_data, ImageSize.Image)
|
||||
wide = convert_image(wide_data, logo_data, ImageSize.ImageWide)
|
||||
tall = convert_image(tall_data, logo_data, ImageSize.Tall)
|
||||
wide = convert_image(wide_data, logo_data, ImageSize.Wide)
|
||||
|
||||
icon = self.__convert_icon(tall)
|
||||
icon.save(str(self.__img_icon(game.app_name)), format=desktop_icon_suffix().upper())
|
||||
icon.save(desktop_icon_path(game.app_name).as_posix(), format=desktop_icon_suffix().upper())
|
||||
|
||||
def save_image(image: QImage, color_path: Path, gray_path: Path):
|
||||
# this is not required if we ever want to re-apply the alpha channel
|
||||
|
@ -344,14 +335,17 @@ class ImageManager(QObject):
|
|||
# add the alpha channel back to the cover
|
||||
image = image.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||
image.save(color_path.as_posix(), format="PNG")
|
||||
# quick way to convert to grayscale
|
||||
# quick way to convert to grayscale, but keep the alpha channel
|
||||
alpha = image.convertToFormat(QImage.Format_Alpha8)
|
||||
image = image.convertToFormat(QImage.Format_Grayscale8)
|
||||
# add the alpha channel back to the grayscale cover
|
||||
image = image.convertToFormat(QImage.Format_ARGB32_Premultiplied)
|
||||
image.setAlphaChannel(alpha)
|
||||
image.save(gray_path.as_posix(), format="PNG")
|
||||
|
||||
save_image(tall, self.__img_tall_color(game.app_name), self.__img_tall_gray(game.app_name))
|
||||
save_image(wide, self.__img_wide_color(game.app_name), self.__img_wide_gray(game.app_name))
|
||||
save_image(icon, image_icon_path(game.app_name), image_icon_path(game.app_name, color=False))
|
||||
save_image(tall, image_tall_path(game.app_name), image_tall_path(game.app_name, color=False))
|
||||
save_image(wide, image_wide_path(game.app_name), image_wide_path(game.app_name, color=False))
|
||||
|
||||
def __compress(self, game: Game, data: Dict) -> None:
|
||||
archive = open(self.__img_cache(game.app_name), "wb")
|
||||
|
@ -395,11 +389,15 @@ class ImageManager(QObject):
|
|||
self.threadpool.start(image_worker, priority)
|
||||
|
||||
def download_image_launch(
|
||||
self, game: Game, callback: Callable[[], None], priority: int, force: bool = False
|
||||
self, game: Game, callback: Callable[[Game], None], priority: int, force: bool = False
|
||||
) -> None:
|
||||
if self.__img_cache(game.app_name).is_file() and not force:
|
||||
return
|
||||
self.download_image(game, callback, priority, force)
|
||||
|
||||
def _callback():
|
||||
callback(game)
|
||||
|
||||
self.download_image(game, _callback, priority, force)
|
||||
|
||||
def download_image_blocking(self, game: Game, force: bool = False) -> None:
|
||||
updates, json_data = self.__prepare_download(game, force)
|
||||
|
@ -408,44 +406,56 @@ class ImageManager(QObject):
|
|||
if updates:
|
||||
self.__download(updates, json_data, game, use_async=True)
|
||||
|
||||
@staticmethod
|
||||
def __get_cover(
|
||||
self, container: Union[Type[QPixmap], Type[QImage]], app_name: str, color: bool
|
||||
container: Union[Type[QPixmap], Type[QImage]], app_name: str, preset: ImageSize.Preset, color: bool,
|
||||
) -> Union[QPixmap, QImage]:
|
||||
ret = container()
|
||||
if not app_name:
|
||||
raise RuntimeError("app_name is an empty string")
|
||||
if color:
|
||||
if self.__img_tall_color(app_name).is_file():
|
||||
ret.load(self.__img_tall_color(app_name).as_posix())
|
||||
if preset.orientation == ImageType.Icon:
|
||||
if image_icon_path(app_name, color).is_file():
|
||||
ret.load(image_icon_path(app_name, color).as_posix())
|
||||
elif preset.orientation == ImageType.Tall:
|
||||
if image_tall_path(app_name, color).is_file():
|
||||
ret.load(image_tall_path(app_name, color).as_posix())
|
||||
elif preset.orientation == ImageType.Wide:
|
||||
if image_wide_path(app_name, color).is_file():
|
||||
ret.load(image_wide_path(app_name, color).as_posix())
|
||||
else:
|
||||
if self.__img_tall_gray(app_name).is_file():
|
||||
ret.load(self.__img_tall_gray(app_name).as_posix())
|
||||
raise RuntimeError("Unknown image preset")
|
||||
if not ret.isNull():
|
||||
ret.setDevicePixelRatio(ImageSize.Image.pixel_ratio)
|
||||
device = ImageSize.Preset(
|
||||
divisor=preset.base.divisor,
|
||||
pixel_ratio=QApplication.instance().devicePixelRatio(),
|
||||
orientation=preset.base.orientation,
|
||||
base=preset
|
||||
)
|
||||
ret.setDevicePixelRatio(preset.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)
|
||||
ret = ret.scaled(device.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
ret.setDevicePixelRatio(device.pixel_ratio)
|
||||
return ret
|
||||
|
||||
def get_pixmap(self, app_name: str, color: bool = True) -> QPixmap:
|
||||
def get_pixmap(self, app_name: str, preset: ImageSize.Preset, 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 preset:
|
||||
@param color: True to load the colored pixmap, False to load the grayscale
|
||||
@return: QPixmap
|
||||
"""
|
||||
pixmap: QPixmap = self.__get_cover(QPixmap, app_name, color)
|
||||
pixmap: QPixmap = self.__get_cover(QPixmap, app_name, preset, color)
|
||||
return pixmap
|
||||
|
||||
def get_image(self, app_name: str, color: bool = True) -> QImage:
|
||||
def get_image(self, app_name: str, preset: ImageSize.Preset, 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 preset:
|
||||
@param color: True to load the colored image, False to load the grayscale
|
||||
@return: QImage
|
||||
"""
|
||||
image: QImage = self.__get_cover(QImage, app_name, color)
|
||||
image: QImage = self.__get_cover(QImage, app_name, preset, color)
|
||||
return image
|
||||
|
|
|
@ -55,6 +55,22 @@ def image_dir() -> Path:
|
|||
return data_dir().joinpath("images")
|
||||
|
||||
|
||||
def image_dir_game(app_name: str) -> Path:
|
||||
return image_dir().joinpath(app_name)
|
||||
|
||||
|
||||
def image_tall_path(app_name: str, color: bool = True) -> Path:
|
||||
return image_dir_game(app_name).joinpath("tall.png" if color else "tall_gray.png")
|
||||
|
||||
|
||||
def image_wide_path(app_name: str, color: bool = True) -> Path:
|
||||
return image_dir_game(app_name).joinpath("wide.png" if color else "wide_gray.png")
|
||||
|
||||
|
||||
def image_icon_path(app_name: str, color: bool = True) -> Path:
|
||||
return image_dir_game(app_name).joinpath("icon.png" if color else "icon_gray.png")
|
||||
|
||||
|
||||
def log_dir() -> Path:
|
||||
return cache_dir().joinpath("logs")
|
||||
|
||||
|
@ -105,19 +121,25 @@ __link_suffix = {
|
|||
"icon": "png",
|
||||
},
|
||||
"Darwin": {
|
||||
"link": "",
|
||||
"icon": "icns",
|
||||
"link": "",
|
||||
"icon": "icns",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def desktop_links_supported() -> bool:
|
||||
supported_systems = [k for (k, v) in __link_suffix.items() if v["link"]]
|
||||
return platform.system() in supported_systems
|
||||
|
||||
|
||||
def desktop_icon_suffix() -> str:
|
||||
return __link_suffix[platform.system()]["icon"]
|
||||
|
||||
|
||||
def desktop_icon_path(app_name: str) -> Path:
|
||||
return image_dir_game(app_name).joinpath(f"desktop_icon.{desktop_icon_suffix()}")
|
||||
|
||||
|
||||
__link_type = {
|
||||
"desktop": desktop_dir(),
|
||||
# lk: for some undocumented reason, on Windows we used the parent directory
|
||||
|
@ -125,8 +147,11 @@ __link_type = {
|
|||
"start_menu": applications_dir().parent if platform.system() == "Windows" else applications_dir(),
|
||||
}
|
||||
|
||||
|
||||
def desktop_link_types() -> List:
|
||||
return list(__link_type.keys())
|
||||
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
|
@ -212,7 +237,7 @@ def create_desktop_link(app_name: str, app_title: str = "", link_name: str = "",
|
|||
app_title = "Rare"
|
||||
link_name = "Rare"
|
||||
else:
|
||||
icon_path = image_dir().joinpath(app_name, f"icon.{desktop_icon_suffix()}")
|
||||
icon_path = desktop_icon_path(app_name)
|
||||
if not app_title or not link_name:
|
||||
logger.error("Missing app_title or link_name")
|
||||
return False
|
||||
|
|
Loading…
Reference in a new issue