diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py
index 91a91a8f..67a62ca9 100644
--- a/rare/components/tabs/games/__init__.py
+++ b/rare/components/tabs/games/__init__.py
@@ -87,7 +87,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
logger.warning("No Unreal engine in library found")
self.ue_name = ""
- self.uninstalled_info_tabs = UninstalledInfoTabs(self.ue_name, self)
+ self.uninstalled_info_tabs = UninstalledInfoTabs(self)
self.uninstalled_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0))
self.addWidget(self.uninstalled_info_tabs)
diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py
index 0cc325f7..0f2b65ba 100644
--- a/rare/components/tabs/games/game_info/game_dlc.py
+++ b/rare/components/tabs/games/game_info/game_dlc.py
@@ -1,5 +1,4 @@
-from PyQt5.QtCore import Qt, pyqtSignal
-from PyQt5.QtGui import QPixmap, QResizeEvent
+from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox
from legendary.models.game import Game
@@ -9,6 +8,7 @@ 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_widget import Ui_GameDlcWidget
from rare.utils.models import InstallOptionsModel
+from rare.widgets.image_widget import ImageWidget
class GameDlc(QWidget, Ui_GameDlc):
@@ -94,13 +94,15 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget):
self.setupUi(self)
self.dlc = dlc
- self.image.setFixedSize(ImageSize.Smaller.size)
+ self.image = ImageWidget(self)
+ self.image.setFixedSize(ImageSize.Smaller)
+ self.dlc_layout.insertWidget(0, self.image)
self.dlc_name.setText(dlc.app_title)
self.version.setText(dlc.app_version())
self.app_name.setText(dlc.app_name)
- self.pixmap = self.image_manager.get_pixmap(dlc.app_name)
+ self.image.setPixmap(self.image_manager.get_pixmap(dlc.app_name))
if installed:
self.action_button.setProperty("uninstall", 1)
@@ -111,26 +113,6 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget):
self.action_button.clicked.connect(self.install_game)
self.action_button.setText(self.tr("Install DLC"))
- def resizeEvent(self, a0: QResizeEvent) -> None:
- self.image.clear()
- super(GameDlcWidget, self).resizeEvent(a0)
- self.setPixmap(self.pixmap)
-
- def setPixmap(self, a0: QPixmap) -> None:
- self.pixmap = a0
- self.image.setPixmap(
- self.pixmap.scaledToHeight(
- self.dlc_info.size().height()
- - (
- self.image.contentsMargins().top()
- + self.image.contentsMargins().bottom()
- + self.image.lineWidth() * 2
- ),
- Qt.SmoothTransformation,
- )
- )
- self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
-
def uninstall_dlc(self):
self.action_button.setDisabled(True)
self.action_button.setText(self.tr("Uninstalling"))
diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py
index 18b4dfd2..a7ed3efd 100644
--- a/rare/components/tabs/games/game_info/game_info.py
+++ b/rare/components/tabs/games/game_info/game_info.py
@@ -40,6 +40,7 @@ from rare.utils.legendary_utils import VerifyWorker
from rare.utils.models import InstallOptionsModel
from rare.utils.steam_grades import SteamWorker
from rare.utils.utils import get_size
+from rare.widgets.image_widget import ImageWidget
logger = getLogger("GameInfo")
@@ -60,6 +61,10 @@ class GameInfo(QWidget, Ui_GameInfo):
self.image_manager = ImageManagerSingleton()
self.game_utils = game_utils
+ self.image = ImageWidget(self)
+ self.image.setFixedSize(ImageSize.Display)
+ self.layout_game_info.insertWidget(0, self.image, alignment=Qt.AlignTop)
+
if platform.system() == "Windows":
self.lbl_grade.setVisible(False)
self.grade.setVisible(False)
@@ -283,11 +288,7 @@ class GameInfo(QWidget, Ui_GameInfo):
self.igame = self.core.get_installed_game(self.game.app_name)
self.title.setTitle(self.game.app_title)
- pixmap = self.image_manager.get_pixmap(self.game.app_name)
- if pixmap.isNull():
- pixmap = self.image_manager.get_pixmap(self.parent().parent().parent().ue_name)
- pixmap = pixmap.scaled(ImageSize.Display.size)
- self.image.setPixmap(pixmap)
+ self.image.setPixmap(self.image_manager.get_pixmap(self.game.app_name, color=True))
self.app_name.setText(self.game.app_name)
if self.igame:
diff --git a/rare/components/tabs/games/game_info/uninstalled_info.py b/rare/components/tabs/games/game_info/uninstalled_info.py
index e41e6d78..79c582c9 100644
--- a/rare/components/tabs/games/game_info/uninstalled_info.py
+++ b/rare/components/tabs/games/game_info/uninstalled_info.py
@@ -17,16 +17,17 @@ from rare.utils.extra_widgets import SideTabWidget
from rare.utils.json_formatter import QJsonModel
from rare.utils.models import InstallOptionsModel
from rare.utils.steam_grades import SteamWorker
+from rare.widgets.image_widget import ImageWidget
class UninstalledInfoTabs(SideTabWidget):
- def __init__(self, ue_default_name, parent=None):
+ def __init__(self, parent=None):
super(UninstalledInfoTabs, self).__init__(show_back=True, parent=parent)
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton()
- self.info = UninstalledInfo(ue_default_name)
+ self.info = UninstalledInfo()
self.info.install_button.setDisabled(self.args.offline)
self.addTab(self.info, self.tr("Information"))
@@ -70,7 +71,7 @@ class GameMetadataView(QTreeView):
class UninstalledInfo(QWidget, Ui_GameInfo):
game: Game
- def __init__(self, ue_default_name, parent=None):
+ def __init__(self, parent=None):
super(UninstalledInfo, self).__init__(parent=parent)
self.setupUi(self)
self.core = LegendaryCoreSingleton()
@@ -78,6 +79,10 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
self.api_results = ApiResultsSingleton()
self.image_manager = ImageManagerSingleton()
+ self.image = ImageWidget(self)
+ self.image.setFixedSize(ImageSize.Display)
+ self.layout_game_info.insertWidget(0, self.image, alignment=Qt.AlignTop)
+
self.install_button.clicked.connect(self.install_game)
if platform.system() != "Windows":
self.steam_worker = SteamWorker(self.core)
@@ -96,7 +101,6 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
self.game_actions_stack.setCurrentIndex(1)
self.game_actions_stack.resize(self.game_actions_stack.minimumSize())
self.lbl_platform.setText(self.tr("Platforms"))
- self.ue_default_name = ue_default_name
def install_game(self):
self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name))
@@ -111,11 +115,7 @@ class UninstalledInfo(QWidget, Ui_GameInfo):
available_platforms.append("macOS")
self.platform.setText(", ".join(available_platforms))
- pixmap = self.image_manager.get_pixmap(game.app_name, color=False)
- if pixmap.isNull():
- pixmap = self.image_manager.get_pixmap(self.ue_default_name, color=False)
- pixmap = pixmap.scaled(ImageSize.Display.size)
- self.image.setPixmap(pixmap)
+ self.image.setPixmap(self.image_manager.get_pixmap(self.game.app_name, color=True))
self.app_name.setText(self.game.app_name)
self.version.setText(self.game.app_version("Windows"))
diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/base_installed_widget.py
index f9d64842..811aec89 100644
--- a/rare/components/tabs/games/game_widgets/base_installed_widget.py
+++ b/rare/components/tabs/games/game_widgets/base_installed_widget.py
@@ -4,12 +4,13 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray
from PyQt5.QtGui import QPixmap
-from PyQt5.QtWidgets import QGroupBox, QMessageBox, QAction, QLabel
+from PyQt5.QtWidgets import QGroupBox, QMessageBox, QAction
from rare.components.tabs.games.game_utils import GameUtils
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.widgets.image_widget import ImageWidget
logger = getLogger("Game")
@@ -63,11 +64,9 @@ class BaseInstalledWidget(QGroupBox):
except AttributeError:
pass
- self.image = QLabel()
- self.image.setFixedSize(ImageSize.Display.size)
- self.image.setPixmap(
- pixmap.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation)
- )
+ self.image = ImageWidget(self)
+ self.image.setFixedSize(ImageSize.Display)
+ self.image.setPixmap(pixmap)
self.game_running = False
self.offline = self.args.offline
self.update_available = False
@@ -144,7 +143,7 @@ class BaseInstalledWidget(QGroupBox):
def reload_image(self):
self.image_manager.download_image_blocking(self.game, force=True)
pm = self.image_manager.get_pixmap(self.game.app_name, color=True)
- self.image.setPixmap(pm.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
+ self.image.setPixmap(pm)
def create_desktop_link(self, type_of_link):
if type_of_link == "desktop":
diff --git a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py
index de216578..8cb1eb8a 100644
--- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py
+++ b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py
@@ -1,10 +1,11 @@
from logging import getLogger
from PyQt5.QtCore import pyqtSignal, Qt
-from PyQt5.QtWidgets import QGroupBox, QLabel, QAction
+from PyQt5.QtWidgets import QGroupBox, QAction
from legendary.models.game import Game
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
+from rare.widgets.image_widget import ImageWidget
logger = getLogger("Uninstalled")
@@ -21,9 +22,9 @@ class BaseUninstalledWidget(QGroupBox):
self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}"
self.core = core
- self.image = QLabel()
- self.image.setFixedSize(ImageSize.Display.size)
- self.image.setPixmap(pixmap.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
+ self.image = ImageWidget(self)
+ self.image.setFixedSize(ImageSize.Display)
+ self.image.setPixmap(pixmap)
self.installing = False
self.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setContentsMargins(0, 0, 0, 0)
@@ -35,7 +36,7 @@ class BaseUninstalledWidget(QGroupBox):
def reload_image(self):
self.image_manager.download_image_blocking(self.game, force=True)
pm = self.image_manager.get_pixmap(self.game.app_name, color=False)
- self.image.setPixmap(pm.scaled(ImageSize.Display.size, transformMode=Qt.SmoothTransformation))
+ self.image.setPixmap(pm)
def install(self):
self.show_uninstalled_info.emit(self.game)
diff --git a/rare/components/tabs/games/game_widgets/installing_game_widget.py b/rare/components/tabs/games/game_widgets/installing_game_widget.py
index 0299e313..93753ec8 100644
--- a/rare/components/tabs/games/game_widgets/installing_game_widget.py
+++ b/rare/components/tabs/games/game_widgets/installing_game_widget.py
@@ -1,41 +1,46 @@
-from PyQt5.QtCore import Qt, QRect
-from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor
-from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap
+from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QWidget
from legendary.models.game import Game
from rare.shared import LegendaryCoreSingleton
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
-from rare.utils.utils import (
- optimal_text_background,
- text_color_for_background,
-)
+from rare.widgets.elide_label import ElideLabel
+from .library_widget import LibraryWidget
-class InstallingGameWidget(QWidget):
+class InstallingGameWidget(QGroupBox):
game: Game = None
def __init__(self):
super(InstallingGameWidget, self).__init__()
+ layout = QVBoxLayout()
self.setObjectName("game_widget_icon")
- self.setProperty("noBorder", 1)
- self.setLayout(QVBoxLayout())
+ self.setContentsMargins(0, 0, 0, 0)
self.core = LegendaryCoreSingleton()
+ self.image_manager = ImageManagerSingleton()
self.pixmap = QPixmap()
- self.image_widget = PaintWidget()
- self.setContentsMargins(0, 0, 0, 0)
- self.image_widget.setFixedSize(ImageSize.Display.size)
- self.layout().addWidget(self.image_widget)
+ self.image = LibraryWidget(parent=self)
+ self.image.setFixedSize(ImageSize.Display)
+ layout.addWidget(self.image)
- self.title_label = QLabel(f"
Error
")
- self.title_label.setAutoFillBackground(False)
- self.title_label.setWordWrap(True)
- self.title_label.setFixedWidth(175)
+ miniwidget = QWidget(self)
+ miniwidget.setFixedWidth(ImageSize.Display.size.width())
minilayout = QHBoxLayout()
- self.setObjectName("game_widget")
- minilayout.addWidget(self.title_label)
- self.layout().addLayout(minilayout)
+ minilayout.setContentsMargins(0, 0, 0, 0)
+ minilayout.setSpacing(0)
+ miniwidget.setLayout(minilayout)
+
+ self.title_label = ElideLabel(f"Error
", parent=miniwidget)
+ self.title_label.setObjectName("game_widget")
+ minilayout.addWidget(self.title_label, stretch=2)
+
+ minilayout.setAlignment(Qt.AlignTop)
+ layout.addWidget(miniwidget)
+
+ self.setLayout(layout)
def set_game(self, app_name):
if not app_name:
@@ -43,73 +48,11 @@ class InstallingGameWidget(QWidget):
return
self.game = self.core.get_game(app_name)
self.title_label.setText(f"{self.game.app_title}
")
-
- self.image_widget.set_game(self.game.app_name)
+ self.image.hideProgress(True)
+ self.image.showProgress(
+ self.image_manager.get_pixmap(app_name, color=True),
+ self.image_manager.get_pixmap(app_name, color=False),
+ )
def set_status(self, s: int):
- self.image_widget.progress = s
- self.image_widget.repaint()
-
-
-class PaintWidget(QWidget):
- color_image: QPixmap
- bw_image: QPixmap
- progress: int = 0
-
- def __init__(self):
- super(PaintWidget, self).__init__()
- self.core = LegendaryCoreSingleton()
- self.image_manager = ImageManagerSingleton()
-
- def set_game(self, app_name: str):
- game = self.core.get_game(app_name, False)
- self.color_image = self.image_manager.get_pixmap(game.app_name, color=False)
- self.color_image = self.color_image.scaled(
- ImageSize.Display.size, transformMode=Qt.SmoothTransformation
- )
- self.setFixedSize(ImageSize.Display.size)
- self.bw_image = self.image_manager.get_pixmap(app_name, color=False)
- self.bw_image = self.bw_image.scaled(
- ImageSize.Display.size, transformMode=Qt.SmoothTransformation
- )
- self.progress = 0
-
- pixel_list = []
- for x in range(self.color_image.width()):
- for y in range(self.color_image.height()):
- # convert pixmap to qimage, get pixel and remove alpha channel
- pixel_list.append(
- self.color_image.toImage().pixelColor(x, y).getRgb()[:-1]
- )
-
- self.rgb_tuple = optimal_text_background(pixel_list)
-
- def paintEvent(self, a0: QPaintEvent) -> None:
- painter = QPainter()
- painter.begin(self)
- painter.setRenderHint(QPainter.Antialiasing)
- painter.drawPixmap(self.rect(), self.bw_image)
-
- w = self.bw_image.width() * self.progress // 100
-
- painter.drawPixmap(
- 0,
- 0,
- w,
- self.color_image.height(),
- self.color_image.copy(QRect(0, 0, w, self.color_image.height())),
- )
-
- # Draw Circle
- pen = QPen(QColor(*self.rgb_tuple), 3)
- painter.setPen(pen)
- painter.setBrush(QColor(*self.rgb_tuple))
- painter.drawEllipse(
- int(self.width() / 2) - 20, int(self.height() / 2) - 20, 40, 40
- )
-
- # draw text
- painter.setPen(QColor(*text_color_for_background(self.rgb_tuple)))
- painter.setFont(QFont(None, 16))
- painter.drawText(a0.rect(), Qt.AlignCenter, f"{self.progress}%")
- painter.end()
+ self.image.updateProgress(s)
diff --git a/rare/components/tabs/games/game_widgets/library_widget.py b/rare/components/tabs/games/game_widgets/library_widget.py
new file mode 100644
index 00000000..f7cc7457
--- /dev/null
+++ b/rare/components/tabs/games/game_widgets/library_widget.py
@@ -0,0 +1,124 @@
+from typing import Optional, Tuple, List
+
+from PyQt5.QtCore import Qt, QRect, QEvent
+from PyQt5.QtGui import QPainter, QPixmap, QResizeEvent, QFontMetrics, QImage, QBrush, QColor
+from PyQt5.QtWidgets import QLabel
+
+from rare.widgets.image_widget import ImageWidget
+
+
+class ProgressLabel(QLabel):
+ def __init__(self, parent):
+ super(ProgressLabel, self).__init__(parent=parent)
+ self.setObjectName(type(self).__name__)
+ self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
+ self.setFrameStyle(QLabel.StyledPanel)
+
+ def setGeometry(self, a0: QRect) -> None:
+ fm = QFontMetrics(self.font())
+ rect = fm.boundingRect(f" {self.text()} ")
+ rect.moveCenter(a0.center())
+ super(ProgressLabel, self).setGeometry(rect)
+
+ @staticmethod
+ def calculateColors(image: QImage) -> Tuple[QColor, QColor]:
+ color: List[int] = [0, 0, 0]
+ # take the two diagonals of the center square section
+ min_d = min(image.width(), image.height())
+ origin_w = (image.width() - min_d) // 2
+ origin_h = (image.height() - min_d) // 2
+ for x, y in zip(range(origin_w, min_d), range(origin_h, min_d)):
+ pixel = image.pixelColor(x, y).getRgb()
+ color = list(map(lambda t: sum(t) // 2, zip(pixel[0:3], color)))
+ # take the V component of the HSV color
+ fg_color = QColor(0, 0, 0) if QColor(*color).value() < 127 else QColor(255, 255, 255)
+ bg_color = QColor(*map(lambda c: 255 - c, color))
+ return bg_color, fg_color
+
+ def setStyleSheetColors(self, bg: QColor, fg: QColor, brd: QColor):
+ sheet = (
+ f"background-color: rgba({bg.red()}, {bg.green()}, {bg.blue()}, 65%);"
+ f"color: rgb({fg.red()}, {fg.green()}, {fg.blue()});"
+ f"border-width: 1px;"
+ f"border-radius: 5%;"
+ f"border-color: rgb({brd.red()}, {brd.green()}, {brd.blue()});"
+ f"font-weight: bold;"
+ f"font-size: 16pt;"
+ )
+ self.setStyleSheet(sheet)
+
+
+class LibraryWidget(ImageWidget):
+ _color_pixmap: Optional[QPixmap] = None
+ _gray_pixmap: Optional[QPixmap] = None
+ # lk: keep percentage to not over-generate the image
+ _progress: int = -1
+
+ def __init__(
+ self,
+ parent=None,
+ ) -> None:
+ super(LibraryWidget, self).__init__(parent)
+ self.progress_label = ProgressLabel(self)
+ self.progress_label.setVisible(False)
+
+ def event(self, e: QEvent) -> bool:
+ if e.type() == QEvent.LayoutRequest:
+ self.progress_label.setGeometry(self.rect())
+ return super(LibraryWidget, self).event(e)
+
+ def resizeEvent(self, a0: QResizeEvent) -> None:
+ if self.progress_label.isVisible():
+ self.progress_label.setGeometry(self.rect())
+ super(LibraryWidget, self).resizeEvent(a0)
+
+ def progressPixmap(self, color: QPixmap, gray: QPixmap, progress: int) -> QPixmap:
+ """
+ Paints the color image over the gray images based on progress percentage
+
+ @param color:
+ @param gray:
+ @param progress:
+ @return:
+ """
+
+ device = QPixmap(color.size())
+ painter = QPainter(device)
+ painter.setRenderHint(QPainter.SmoothPixmapTransform, self._smooth_transform)
+ painter.setCompositionMode(QPainter.CompositionMode_Source)
+ # lk: Vertical loading
+ # prog_h = (device.height() * progress // 100)
+ # brush = QBrush(gray)
+ # painter.fillRect(device.rect().adjusted(0, 0, 0, -prog_h), brush)
+ # brush.setTexture(color)
+ # painter.fillRect(device.rect().adjusted(0, device.height() - prog_h, 0, 0), brush)
+ # lk: Horizontal loading
+ prog_w = device.width() * progress // 100
+ brush = QBrush(gray)
+ painter.fillRect(device.rect().adjusted(prog_w, 0, 0, 0), brush)
+ brush.setTexture(color)
+ painter.fillRect(device.rect().adjusted(0, 0, prog_w - device.width(), 0), brush)
+ painter.end()
+ device.setDevicePixelRatio(color.devicePixelRatioF())
+ return device
+
+ def showProgress(self, color_pm: QPixmap, gray_pm: QPixmap) -> None:
+ self._color_pixmap = color_pm
+ self._gray_pixmap = gray_pm
+ bg_color, fg_color = self.progress_label.calculateColors(color_pm.toImage())
+ self.progress_label.setStyleSheetColors(bg_color, fg_color, fg_color)
+ self.progress_label.setVisible(True)
+ self.progress_label.update()
+ self.updateProgress(0)
+
+ def updateProgress(self, progress: int):
+ if progress > self._progress:
+ self._progress = progress
+ self.progress_label.setText(f"{progress:02}%")
+ self.setPixmap(self.progressPixmap(self._color_pixmap, self._gray_pixmap, progress))
+
+ def hideProgress(self, stopped: bool):
+ self._color_pixmap = None
+ self._gray_pixmap = None
+ self.progress_label.setVisible(stopped)
+ self._progress = -1
diff --git a/rare/ui/components/tabs/games/game_info/game_dlc_widget.py b/rare/ui/components/tabs/games/game_info/game_dlc_widget.py
index 723a14f5..9c37b2d2 100644
--- a/rare/ui/components/tabs/games/game_info/game_dlc_widget.py
+++ b/rare/ui/components/tabs/games/game_info/game_dlc_widget.py
@@ -25,12 +25,6 @@ class Ui_GameDlcWidget(object):
GameDlcWidget.setFrameShadow(QtWidgets.QFrame.Plain)
self.dlc_layout = QtWidgets.QHBoxLayout(GameDlcWidget)
self.dlc_layout.setObjectName("dlc_layout")
- self.image = QtWidgets.QLabel(GameDlcWidget)
- self.image.setFrameShape(QtWidgets.QFrame.StyledPanel)
- self.image.setFrameShadow(QtWidgets.QFrame.Plain)
- self.image.setText("image")
- self.image.setObjectName("image")
- self.dlc_layout.addWidget(self.image)
self.dlc_info = QtWidgets.QWidget(GameDlcWidget)
self.dlc_info.setObjectName("dlc_info")
self.dlc_info_layout = QtWidgets.QFormLayout(self.dlc_info)
@@ -105,7 +99,7 @@ class Ui_GameDlcWidget(object):
self.action_button.setText("Action")
self.action_button.setObjectName("action_button")
self.dlc_layout.addWidget(self.action_button, 0, QtCore.Qt.AlignTop)
- self.dlc_layout.setStretch(2, 1)
+ self.dlc_layout.setStretch(1, 1)
self.retranslateUi(GameDlcWidget)
QtCore.QMetaObject.connectSlotsByName(GameDlcWidget)
diff --git a/rare/ui/components/tabs/games/game_info/game_dlc_widget.ui b/rare/ui/components/tabs/games/game_info/game_dlc_widget.ui
index 33ae4ef8..ff228bb4 100644
--- a/rare/ui/components/tabs/games/game_info/game_dlc_widget.ui
+++ b/rare/ui/components/tabs/games/game_info/game_dlc_widget.ui
@@ -25,20 +25,7 @@
QFrame::Plain
-
- -
-
-
- QFrame::StyledPanel
-
-
- QFrame::Plain
-
-
- image
-
-
-
+
-
diff --git a/rare/ui/components/tabs/games/game_info/game_info.py b/rare/ui/components/tabs/games/game_info/game_info.py
index 755b0a2f..25c80961 100644
--- a/rare/ui/components/tabs/games/game_info/game_info.py
+++ b/rare/ui/components/tabs/games/game_info/game_info.py
@@ -17,12 +17,6 @@ class Ui_GameInfo(object):
GameInfo.resize(791, 583)
self.layout_game_info = QtWidgets.QHBoxLayout(GameInfo)
self.layout_game_info.setObjectName("layout_game_info")
- self.image = QtWidgets.QLabel(GameInfo)
- self.image.setFrameShape(QtWidgets.QFrame.StyledPanel)
- self.image.setFrameShadow(QtWidgets.QFrame.Sunken)
- self.image.setText("")
- self.image.setObjectName("image")
- self.layout_game_info.addWidget(self.image, 0, QtCore.Qt.AlignTop)
self.layout_game_info_form = QtWidgets.QGridLayout()
self.layout_game_info_form.setContentsMargins(6, 6, 6, 6)
self.layout_game_info_form.setSpacing(12)
diff --git a/rare/ui/components/tabs/games/game_info/game_info.ui b/rare/ui/components/tabs/games/game_info/game_info.ui
index a97bfa66..20a891ac 100644
--- a/rare/ui/components/tabs/games/game_info/game_info.ui
+++ b/rare/ui/components/tabs/games/game_info/game_info.ui
@@ -14,19 +14,6 @@
Game Info
-
-
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
-
-
-
-
-
diff --git a/rare/utils/utils.py b/rare/utils/utils.py
index 1387727d..6501591d 100644
--- a/rare/utils/utils.py
+++ b/rare/utils/utils.py
@@ -1,11 +1,10 @@
-import math
import os
import platform
import shlex
import subprocess
import sys
from logging import getLogger
-from typing import List, Tuple
+from typing import List
import qtawesome
import requests
@@ -306,35 +305,6 @@ def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link=
return False
-def optimal_text_background(image: list) -> Tuple[int, int, int]:
- """
- Finds an optimal background color for text on the image by calculating the
- average color of the image and inverting it.
-
- The image list is supposed to be a one-dimensional list of arbitrary length
- containing RGB tuples, ranging from 0 to 255.
- """
- # cursed, I know
- average = map(lambda value: value // len(image), map(sum, zip(*image)))
- inverted = map(lambda value: 255 - value, average)
- return tuple(inverted)
-
-
-def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, int, int]:
- """
- Calculates whether a black or white text color would fit better for the
- given background, and returns that color. This is done by calculating the
- luminance and simple comparing of bounds
- """
- # see https://alienryderflex.com/hsp.html
- (red, green, blue) = background
- luminance = math.sqrt(0.299 * red ** 2 + 0.587 * green ** 2 + 0.114 * blue ** 2)
- if luminance < 127:
- return 255, 255, 255
- else:
- return 0, 0, 0
-
-
class WineResolverSignals(QObject):
result_ready = pyqtSignal(str)
diff --git a/rare/widgets/image_widget.py b/rare/widgets/image_widget.py
new file mode 100644
index 00000000..c989bfd3
--- /dev/null
+++ b/rare/widgets/image_widget.py
@@ -0,0 +1,144 @@
+from enum import Enum
+from typing import Tuple, Optional, Union
+
+from PyQt5.QtCore import Qt, QRectF
+from PyQt5.QtGui import (
+ QPaintEvent,
+ QPainter,
+ QPixmap,
+ QTransform,
+ QBrush,
+ QPalette,
+ QPainterPath,
+ QLinearGradient,
+ QColor,
+)
+from PyQt5.QtWidgets import QWidget
+
+from rare.shared.image_manager import ImageSize
+
+OverlayPath = Tuple[QPainterPath, Union[QColor, QLinearGradient]]
+
+
+class ImageWidget(QWidget):
+ class Border(Enum):
+ Rounded = 0
+ Squared = 1
+
+ _pixmap: Optional[QPixmap] = None
+ _opacity: float = 1.0
+ _transform: QTransform
+ _smooth_transform: bool = False
+ _rounded_overlay: Optional[OverlayPath] = None
+ _squared_overlay: Optional[OverlayPath] = None
+ _image_size: Optional[ImageSize.Preset] = None
+
+ def __init__(self, parent=None) -> None:
+ super(ImageWidget, self).__init__(parent=parent)
+ self.setObjectName(type(self).__name__)
+ self.setContentsMargins(0, 0, 0, 0)
+ self.paint_image = self.paint_image_empty
+ self.paint_overlay = self.paint_overlay_rounded
+
+ def setOpacity(self, value: float) -> None:
+ self._opacity = value
+ self.update()
+
+ def setPixmap(self, pixmap: QPixmap) -> None:
+ if not pixmap.isNull():
+ self._pixmap = pixmap
+ self.paint_image = self.paint_image_cover
+ if not self._image_size:
+ self._transform = QTransform().scale(
+ 1 / pixmap.devicePixelRatioF(),
+ 1 / pixmap.devicePixelRatioF(),
+ )
+ else:
+ self._transform = QTransform().scale(
+ 1 / pixmap.devicePixelRatioF() / self._image_size.divisor,
+ 1 / pixmap.devicePixelRatioF() / self._image_size.divisor,
+ )
+ self.update()
+
+ def setFixedSize(self, a0: ImageSize.Preset) -> None:
+ self._squared_overlay = None
+ self._rounded_overlay = None
+ self._image_size = a0
+ self._smooth_transform = a0.smooth
+ super(ImageWidget, self).setFixedSize(a0.size)
+
+ def setBorder(self, border: Border):
+ if border == ImageWidget.Border.Rounded:
+ self.paint_overlay = self.paint_overlay_rounded
+ else:
+ self.paint_overlay = self.paint_overlay_squared
+ self.update()
+
+ def _generate_squared_overlay(self) -> OverlayPath:
+ if self._image_size is not None and self._squared_overlay is not None:
+ return self._squared_overlay
+ path = QPainterPath()
+ path.addRect(0, 0, self.width(), self.height())
+ border = 2
+ inner_path = QPainterPath()
+ inner_path.addRect(
+ QRectF(
+ border,
+ border,
+ self.width() - border * 2,
+ self.height() - border * 2,
+ )
+ )
+ gradient = QLinearGradient(0, 0, 0, self.height())
+ gradient.setColorAt(0.0, Qt.black)
+ gradient.setColorAt(1.0, Qt.transparent)
+ self._squared_overlay = path.subtracted(inner_path), gradient
+ return self._squared_overlay
+
+ def _generate_rounded_overlay(self) -> OverlayPath:
+ if self._image_size is not None and self._rounded_overlay is not None:
+ return self._rounded_overlay
+ # lk: the '-1' and '+1' are adjustments required for anti-aliasing
+ # lk: otherwise vertical lines would appear at the edges
+ path = QPainterPath()
+ path.addRect(-1, -1, self.width() + 2, self.height() + 2)
+ rounded_path = QPainterPath()
+ rounded_path.addRoundedRect(
+ QRectF(0, 0, self.width(), self.height()),
+ self.height() * 0.045,
+ self.height() * 0.045,
+ )
+ self._rounded_overlay = path.subtracted(rounded_path), QColor(0, 0, 0, 0)
+ return self._rounded_overlay
+
+ def paint_image_empty(self, painter: QPainter, a0: QPaintEvent) -> None:
+ # when pixmap object is not available yet, show a gray rectangle
+ painter.setOpacity(0.5 * self._opacity)
+ painter.fillRect(a0.rect(), Qt.darkGray)
+
+ def paint_image_cover(self, painter: QPainter, a0: QPaintEvent) -> None:
+ painter.setOpacity(self._opacity)
+ brush = QBrush(self._pixmap)
+ # downscale the image during painting to fit the pixelratio
+ brush.setTransform(self._transform)
+ painter.fillRect(a0.rect(), brush)
+
+ def paint_overlay_rounded(self, painter: QPainter, a0: QPaintEvent) -> None:
+ painter.setRenderHint(QPainter.Antialiasing, True)
+ painter.setOpacity(1.0)
+ painter.setCompositionMode(QPainter.CompositionMode_Source)
+ overlay, _ = self._generate_rounded_overlay()
+ painter.fillPath(overlay, self.palette().color(QPalette.Background))
+
+ def paint_overlay_squared(self, painter: QPainter, a0: QPaintEvent) -> None:
+ painter.setRenderHint(QPainter.Antialiasing, False)
+ painter.setOpacity(self._opacity)
+ painter.fillPath(*self._generate_squared_overlay())
+
+ def paintEvent(self, a0: QPaintEvent) -> None:
+ painter = QPainter(self)
+ # helps with better image quality
+ painter.setRenderHint(QPainter.SmoothPixmapTransform, self._smooth_transform)
+ self.paint_image(painter, a0)
+ self.paint_overlay(painter, a0)
+ painter.end()