From b3cc412142cdc780838983487e5d7e9debc85f07 Mon Sep 17 00:00:00 2001 From: Dummerle Date: Sat, 28 Aug 2021 00:16:17 +0200 Subject: [PATCH] Add an installing game widget to icon list --- rare/components/tab_widget.py | 5 ++ rare/components/tabs/downloads/__init__.py | 2 + rare/components/tabs/games/game_list.py | 13 ++- .../game_widgets/installing_game_widget.py | 82 +++++++++++++++++++ rare/utils/utils.py | 36 ++++++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 rare/components/tabs/games/game_widgets/installing_game_widget.py diff --git a/rare/components/tab_widget.py b/rare/components/tab_widget.py index 43f86a96..497b4d6d 100644 --- a/rare/components/tab_widget.py +++ b/rare/components/tab_widget.py @@ -114,6 +114,9 @@ class TabWidget(QTabWidget): QShortcut("Alt+3", self).activated.connect(lambda: self.setCurrentIndex(2)) QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5)) + self.downloadTab.dl_status.connect( + self.games_tab.default_widget.game_list.installing_widget.image_widget.set_status) + # TODO; maybe pass InstallOptionsModel only, not split arguments def install_game(self, options: InstallOptionsModel, update=False, silent=False): install_dialog = InstallDialog(self.core, @@ -132,6 +135,7 @@ class TabWidget(QTabWidget): self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")) self.setCurrentIndex(1) self.downloadTab.install_game(download_item) + self.games_tab.default_widget.game_list.start_download(download_item.options.app_name) def game_imported(self, app_name: str): igame = self.core.get_installed_game(app_name) @@ -167,6 +171,7 @@ class TabWidget(QTabWidget): if update_list[0]: self.games_tab.default_widget.game_list.update_list(update_list[1]) self.cloud_saves.reload(update_list[1]) + self.games_tab.default_widget.game_list.installing_widget.setVisible(False) downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys()) self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")) diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index c06a7e67..af02a5a7 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -21,6 +21,7 @@ class DownloadTab(QWidget): finished = pyqtSignal(tuple) thread: QThread dl_queue = [] + dl_status = pyqtSignal(int) def __init__(self, core: LegendaryCore, updates: list, parent): super(DownloadTab, self).__init__(parent=parent) @@ -184,6 +185,7 @@ class DownloadTab(QWidget): self.downloaded.setText( self.tr("Downloaded") + f": {get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}") self.time_left.setText(self.tr("Time left: ") + self.get_time(ui_update.estimated_time_left)) + self.dl_status.emit(int(100 * ui_update.total_downloaded / self.analysis.dl_size)) def get_time(self, seconds: int) -> str: return str(datetime.timedelta(seconds=seconds)) diff --git a/rare/components/tabs/games/game_list.py b/rare/components/tabs/games/game_list.py index 36119932..5fafe38b 100644 --- a/rare/components/tabs/games/game_list.py +++ b/rare/components/tabs/games/game_list.py @@ -8,6 +8,7 @@ from custom_legendary.core import LegendaryCore from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget from rare.components.tabs.games.game_widgets.installed_icon_widget import GameWidgetInstalled from rare.components.tabs.games.game_widgets.installed_list_widget import InstalledListWidget +from rare.components.tabs.games.game_widgets.installing_game_widget import InstallingGameWidget from rare.components.tabs.games.game_widgets.uninstalled_icon_widget import IconWidgetUninstalled from rare.components.tabs.games.game_widgets.uninstalled_list_widget import ListWidgetUninstalled from rare.utils.extra_widgets import FlowLayout @@ -33,7 +34,7 @@ class GameList(QStackedWidget): self.core = core self.setObjectName("list_widget") self.offline = offline - + self.installing_widget = InstallingGameWidget(self.core.get_game("CrabEA")) self.settings = QSettings() icon_view = self.settings.value("icon_view", True, bool) self.procs = [] @@ -81,6 +82,10 @@ class GameList(QStackedWidget): self.mac_games = [] self.installed = sorted(self.core.get_installed_list(), key=lambda x: x.title) + self.installing_widget = InstallingGameWidget(self.core.get_game("CrabEA")) + self.icon_layout.addWidget(self.installing_widget) + self.installing_widget.setVisible(False) + # Installed Games for igame in self.installed: icon_widget, list_widget = self.add_installed_widget(igame) @@ -123,6 +128,10 @@ class GameList(QStackedWidget): if filter_games := self.settings.value("filter", "", str): self.filter(filter_games) + def start_download(self, app_name): + self.installing_widget.set_game(self.core.get_game(app_name)) + self.installing_widget.setVisible(True) + def add_uninstalled_widget(self, game): pixmap = get_uninstalled_pixmap(game.app_name) if pixmap.isNull(): @@ -347,6 +356,7 @@ class GameList(QStackedWidget): i_widget, list_widget = self.widgets[name] self.icon_layout.addWidget(i_widget) self.list_layout.addWidget(list_widget) + self.installing_widget.setVisible(False) def _update_games(self): # new layouts to remove from old layout @@ -354,6 +364,7 @@ class GameList(QStackedWidget): # QWidget().setLayout(self.list_layout) list_layout = QVBoxLayout() + icon_layout.addWidget(self.installing_widget) for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title): i_widget, l_widget = self.widgets[igame.app_name] icon_layout.addWidget(i_widget) diff --git a/rare/components/tabs/games/game_widgets/installing_game_widget.py b/rare/components/tabs/games/game_widgets/installing_game_widget.py new file mode 100644 index 00000000..f3ce2de8 --- /dev/null +++ b/rare/components/tabs/games/game_widgets/installing_game_widget.py @@ -0,0 +1,82 @@ +from PyQt5.QtCore import Qt, QRect +from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QLabel, QHBoxLayout, QWidget + +from custom_legendary.models.game import Game +from rare.utils.utils import get_pixmap, get_uninstalled_pixmap, optimal_text_background, text_color_for_background + + +class InstallingGameWidget(QGroupBox): + def __init__(self, game: Game): + super(InstallingGameWidget, self).__init__() + self.setObjectName("game_widget_icon") + + self.setLayout(QVBoxLayout()) + + self.pixmap = get_pixmap(game.app_name) + w = 200 + self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation) + + self.image_widget = PaintWidget(self.pixmap, game.app_name) + self.image_widget.setFixedSize(w, int(w * 4 / 3)) + self.layout().addWidget(self.image_widget) + + self.title_label = QLabel(f"

{game.app_title}

") + self.title_label.setAutoFillBackground(False) + self.title_label.setWordWrap(True) + self.title_label.setFixedWidth(175) + minilayout = QHBoxLayout() + self.title_label.setObjectName("game_widget") + minilayout.addWidget(self.title_label) + self.layout().addLayout(minilayout) + + def set_game(self, game: Game): + self.title_label.setText(f"

{game.app_title}

") + self.pixmap = get_pixmap(game.app_name) + w = 200 + self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation) + self.image_widget.set_game(self.pixmap, game.app_name) + + +class PaintWidget(QWidget): + def __init__(self, image: QPixmap, app_name): + super(PaintWidget, self).__init__() + self.set_game(image, app_name) + + def set_game(self, pixmap: QPixmap, app_name): + self.image = pixmap + self.setFixedSize(self.image.size()) + self.new_image = get_uninstalled_pixmap(app_name) + self.status = 0 + pixel_list = [] + for x in range(self.image.width()): + for y in range(self.image.height()): + # convert pixmap to qimage, get pixel and remove alpha channel + pixel_list.append(self.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.drawPixmap(self.rect(), self.image) + + w = self.image.width() * (1 - self.status / 100) + painter.drawPixmap(self.image.width() - w, 0, w, self.image.height(), + self.new_image.copy(QRect(self.image.width() - w, 0, w, self.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, str(self.status) + "%") + painter.end() + + def set_status(self, s: int): + self.status = s + self.repaint() diff --git a/rare/utils/utils.py b/rare/utils/utils.py index e530ab19..f0830558 100644 --- a/rare/utils/utils.py +++ b/rare/utils/utils.py @@ -1,9 +1,11 @@ import json +import math import os import platform import shutil import sys from logging import getLogger +from typing import Tuple import requests from PIL import Image, UnidentifiedImageError @@ -376,3 +378,37 @@ def get_uninstalled_pixmap(app_name: str) -> QPixmap: else: pixmap = QPixmap() return pixmap + + +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