diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2ae923f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/images/ diff --git a/Rare.py b/Rare.py index e37e6a12..790ceefa 100644 --- a/Rare.py +++ b/Rare.py @@ -425,46 +425,48 @@ def startMainProcess(loggedIn=False): # Since some games have the background and logo split into two files, # we have to do some extra logic to combine those if not os.path.isfile('images/' + game["app_name"] + '/FinalArt.png'): - print('Scaling cover for ' + game["app_name"]) - # First off, check if the extra logo file is even there - if os.path.isfile('images/' + game["app_name"] + '/DieselGameBoxLogo.png'): - # Load in the two images that need to be combined - bg = Image.open('images/' + game["app_name"] + '/DieselGameBoxTall.png') - # To make sure the background is actually horizontal (3/4) (looking at you Celeste), resize the image - bg = bg.resize((int(bg.size[1] * 3 / 4), bg.size[1])) - # Since the logo is transparent, we have to convert it to RGBA - logo = Image.open('images/' + game["app_name"] + '/DieselGameBoxLogo.png').convert('RGBA') - # Resize the logo to be ~ 3/4 as wide as the background (EGL does something like this) - wpercent = ((bg.size[0] * (3 / 4)) / float(logo.size[0])) - hsize = int((float(logo.size[1]) * float(wpercent))) - logo = logo.resize((int(bg.size[0] * (3 / 4)), hsize), Image.ANTIALIAS) - # Calculate where the image has to be placed - pasteX = int((bg.size[0] - logo.size[0]) / 2) - pasteY = int((bg.size[1] - logo.size[1]) / 2) - # And finally copy the background and paste in the image - finalArt = bg.copy() - finalArt.paste(logo, (pasteX, pasteY), logo) - # Write out the file - finalArt.save('images/' + game["app_name"] + '/FinalArt.png') - - # And we have to do part of that again - # since the cover for an uninstalled game has the logo half-transparent - logoCopy = logo.copy() - logoCopy.putalpha(int(256 * 3 / 4)) - logo.paste(logoCopy, logo) - uninstalledArt = bg.copy() - uninstalledArt.paste(logo, (pasteX, pasteY), logo) - uninstalledArt = uninstalledArt.convert('L') - uninstalledArt.save('images/' + game["app_name"] + '/UninstalledArt.png') - # And if the logo and background aren't split - else: - # We just open up the background and save that as the final image - finalArt = Image.open('images/' + game["app_name"] + '/DieselGameBoxTall.png') - finalArt.save('images/' + game["app_name"] + '/FinalArt.png') - # And same with the grayscale one - uninstalledArt = finalArt.convert('L') - uninstalledArt.save('images/' + game["app_name"] + '/UninstalledArt.png') + try: + print('Scaling cover for ' + game["app_name"]) + # First off, check if the extra logo file is even there + if os.path.isfile('images/' + game["app_name"] + '/DieselGameBoxLogo.png'): + # Load in the two images that need to be combined + bg = Image.open('images/' + game["app_name"] + '/DieselGameBoxTall.png') + # To make sure the background is actually horizontal (3/4) (looking at you Celeste), resize the image + bg = bg.resize((int(bg.size[1] * 3 / 4), bg.size[1])) + # Since the logo is transparent, we have to convert it to RGBA + logo = Image.open('images/' + game["app_name"] + '/DieselGameBoxLogo.png').convert('RGBA') + # Resize the logo to be ~ 3/4 as wide as the background (EGL does something like this) + wpercent = ((bg.size[0] * (3 / 4)) / float(logo.size[0])) + hsize = int((float(logo.size[1]) * float(wpercent))) + logo = logo.resize((int(bg.size[0] * (3 / 4)), hsize), Image.ANTIALIAS) + # Calculate where the image has to be placed + pasteX = int((bg.size[0] - logo.size[0]) / 2) + pasteY = int((bg.size[1] - logo.size[1]) / 2) + # And finally copy the background and paste in the image + finalArt = bg.copy() + finalArt.paste(logo, (pasteX, pasteY), logo) + # Write out the file + finalArt.save('images/' + game["app_name"] + '/FinalArt.png') + # And we have to do part of that again + # since the cover for an uninstalled game has the logo half-transparent + logoCopy = logo.copy() + logoCopy.putalpha(int(256 * 3 / 4)) + logo.paste(logoCopy, logo) + uninstalledArt = bg.copy() + uninstalledArt.paste(logo, (pasteX, pasteY), logo) + uninstalledArt = uninstalledArt.convert('L') + uninstalledArt.save('images/' + game["app_name"] + '/UninstalledArt.png') + # And if the logo and background aren't split + else: + # We just open up the background and save that as the final image + finalArt = Image.open('images/' + game["app_name"] + '/DieselGameBoxTall.png') + finalArt.save('images/' + game["app_name"] + '/FinalArt.png') + # And same with the grayscale one + uninstalledArt = finalArt.convert('L') + uninstalledArt.save('images/' + game["app_name"] + '/UninstalledArt.png') + except: + pass #If the user had to login first, the QApplication will already be running, so we don't have to start it again if not loggedIn: # Start GUI stuff diff --git a/Rare/FlowLayout.py b/Rare/FlowLayout.py new file mode 100644 index 00000000..66441fce --- /dev/null +++ b/Rare/FlowLayout.py @@ -0,0 +1,92 @@ +from PyQt5 import Qt +from PyQt5.QtCore import QRect, QPoint, QSize +from PyQt5.QtWidgets import QSizePolicy, QLayout + + +class FlowLayout(QLayout): + def __init__(self, parent=None, margin=0, spacing=-1): + super(FlowLayout, self).__init__(parent) + + if parent is not None: + self.setContentsMargins(margin, margin, margin, margin) + + self.setSpacing(spacing) + + self.itemList = [] + + def __del__(self): + item = self.takeAt(0) + while item: + item = self.takeAt(0) + + def addItem(self, item): + self.itemList.append(item) + + def count(self): + return len(self.itemList) + + def itemAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList[index] + + return None + + def takeAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList.pop(index) + + return None + + def expandingDirections(self): + return Qt.Orientations(Qt.Orientation(0)) + + def hasHeightForWidth(self): + return True + + def heightForWidth(self, width): + height = self.doLayout(QRect(0, 0, width, 0), True) + return height + + def setGeometry(self, rect): + super(FlowLayout, self).setGeometry(rect) + self.doLayout(rect, False) + + def sizeHint(self): + return self.minimumSize() + + def minimumSize(self): + size = QSize() + + for item in self.itemList: + size = size.expandedTo(item.minimumSize()) + + margin, _, _, _ = self.getContentsMargins() + + size += QSize(2 * margin, 2 * margin) + return size + + def doLayout(self, rect, testOnly): + x = rect.x() + y = rect.y() + lineHeight = 0 + + for item in self.itemList: + wid = item.widget() + spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, + Qt.Horizontal) + spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, + Qt.Vertical) + nextX = x + item.sizeHint().width() + spaceX + if nextX - spaceX > rect.right() and lineHeight > 0: + x = rect.x() + y = y + lineHeight + spaceY + nextX = x + item.sizeHint().width() + spaceX + lineHeight = 0 + + if not testOnly: + item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) + + x = nextX + lineHeight = max(lineHeight, item.sizeHint().height()) + + return y + lineHeight - rect.y() diff --git a/Rare/GameWidget.py b/Rare/GameWidget.py new file mode 100644 index 00000000..52b810a5 --- /dev/null +++ b/Rare/GameWidget.py @@ -0,0 +1,44 @@ +import qtawesome as qta +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton + + +class GameWidget(QWidget): + def __init__(self, game): + super(GameWidget, self).__init__() + # self.setStyleSheet("border:1px solid rgb(0, 0, 0)") + + self.title = game.title + self.version = game.version + + self.layout = QHBoxLayout() + + + self.pixmap = QPixmap(f"../images/{game.app_name}/FinalArt.png") + self.pixmap = self.pixmap.scaled(240, 320) + self.image = QLabel() + self.image.setPixmap(self.pixmap) + self.layout.addWidget(self.image) + + ##Layout on the right + self.childLayout = QVBoxLayout() + + self.childLayout.addWidget(QLabel(f"

{self.title}

")) + play_icon = qta.icon('fa5s.play') + + self.launch_button = QPushButton(play_icon, "Launch") + self.launch_button.clicked.connect(self.launch) + self.wine_rating = QLabel("Wine Rating: Comming Soon") + self.version_label = QLabel("Version: " + str(self.version)) + + self.childLayout.addWidget(self.launch_button) + self.childLayout.addWidget(self.wine_rating) + self.childLayout.addWidget(self.version_label) + + self.childLayout.addStretch(1) + # self.layout.addWidget(QLabel(game.title)) + self.layout.addLayout(self.childLayout) + self.setLayout(self.layout) + + def launch(self): + print(f"launch {self.title}") diff --git a/Rare/Main.py b/Rare/Main.py new file mode 100644 index 00000000..081acbe7 --- /dev/null +++ b/Rare/Main.py @@ -0,0 +1,79 @@ +import json +import os +import subprocess + +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QStackedLayout, QScrollArea + +import Rare +from Rare import FlowLayout +from Rare.ShowGame import GameWidget + + +class BodyGames(QScrollArea): + def __init__(self, allGames, installedGames): + super().__init__() + self.allGames = allGames + self.installedGames = installedGames + self.initUI() + + def initUI(self): + self.layout = FlowLayout.FlowLayout() + self.setLayout(self.layout) + for game in self.allGames: + self.layout.addWidget(GameWidget(game=game, installed=game['app_name'] in self.installedGames)) + + def addImage(self, pathToImage, layout, x, y): + self.label = Rare.ExtendedQLabel() + self.label.setPixmap(QPixmap(pathToImage).scaled(240, 320)) + self.label.clicked.connect(lambda: self.clickedGame(pathToImage.split('/', 2)[1])) + self.label.rightClicked.connect(lambda: self.gameSettings(pathToImage.split('/', 2)[1])) + layout.addWidget(self.label, x, y) + + def clickedGame(self, game): + self.game = game + if self.game in self.installedGames: + self.launchGame(self.game) + else: + self.installGame(self.game) + + # def launchGame(self, game): + # self.window = LaunchWindow(game) + # self.window.show() + # self.window.outputWindow.appendPlainText('> legendary launch ' + game + '\n') + # self.window.reader.start('legendary', ['launch', game]) + + def installGame(self, game): + self.window = Rare.InstallWindow(game) + self.window.show() + + def gameSettings(self, game): + self.window = Rare.GameSettingsWindow(game) + self.window.show() + + +class Body(QStackedLayout): + def __init__(self): + super(Body, self).__init__() + + all, installed = self.get_games() + self.addWidget(BodyGames(all, installed)) + + def get_games(self): + all_games = [] + for filename in os.listdir(os.path.expanduser("~") + '/.config/legendary/metadata/'): + with open(os.path.expanduser("~") + '/.config/legendary/metadata/' + filename, 'r') as f: + game = json.load(f) + all_games.append(game) + try: + os.mkdir('images/' + game["app_name"]) + except OSError: + pass + + # print('Parsing installed games') + installedGames = [] + for line in subprocess.Popen('legendary list-installed --csv | tail -n +2', shell=True, + stdout=subprocess.PIPE, universal_newlines=True).stdout: + installedGames.append(line.split(',', 1)[0]) + + return all_games, installedGames diff --git a/Rare/MainRare.py b/Rare/MainRare.py new file mode 100644 index 00000000..4f24e5b6 --- /dev/null +++ b/Rare/MainRare.py @@ -0,0 +1,46 @@ +import sys + +from PyQt5.QtWidgets import QTabWidget, QMainWindow, QWidget, QApplication + +from Rare.TabWidgets import Settings, GameListInstalled, BrowserTab, GameListUninstalled, UpdateList + + +class MainWindow(QMainWindow): + + def __init__(self): + super().__init__() + self.setWindowTitle("Rare") + self.setGeometry(0, 0, 800, 600) + self.setCentralWidget(TabWidget(self)) + self.show() + + +class TabWidget(QTabWidget): + + def __init__(self, parent): + super(QWidget, self).__init__(parent) + + self.game_list = GameListInstalled(self) + self.addTab(self.game_list, "Games") + + self.uninstalled_games = GameListUninstalled(self) + self.addTab(self.uninstalled_games, "Install Games") + + self.update_tab = UpdateList(self) + self.addTab(self.update_tab, "Updates") + + self.browser = BrowserTab(self) + self.addTab(self.browser, "Store") + + self.settings = Settings(self) + self.addTab(self.settings, "Settings") + + +def main(): + app = QApplication(sys.argv) + ex = MainWindow() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main() diff --git a/Rare/TabWidgets.py b/Rare/TabWidgets.py new file mode 100644 index 00000000..ac6aa5f2 --- /dev/null +++ b/Rare/TabWidgets.py @@ -0,0 +1,56 @@ +from PyQt5.QtCore import QUrl, Qt +from PyQt5.QtWebEngineWidgets import QWebEngineView +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea + +from Rare.utils.legendary import get_installed +from Rare.GameWidget import GameWidget + + +class BrowserTab(QWebEngineView): + def __init__(self, parent): + super(BrowserTab, self).__init__(parent=parent) + self.load(QUrl("https://www.epicgames.com/store/")) + self.show() + + +class Settings(QWidget): + def __init__(self, parent): + super(Settings, self).__init__(parent=parent) + self.layout = QVBoxLayout() + label = QLabel() + print(label.fontInfo().pixelSize()) + self.layout.addWidget(QLabel("

Settings

")) + self.layout.addWidget(QLabel("Coming soon")) + self.layout.addStretch(1) + self.setLayout(self.layout) + + +class GameListInstalled(QScrollArea): + def __init__(self, parent): + super(GameListInstalled, self).__init__(parent=parent) + self.widget = QWidget() + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.layout = QVBoxLayout() + for i in get_installed(): + widget = GameWidget(i) + #widget.setFixedSize(240, 340) + self.layout.addWidget(widget) + + self.widget.setLayout(self.layout) + self.setWidget(self.widget) + + +class GameListUninstalled(QScrollArea): + def __init__(self, parent): + super(GameListUninstalled, self).__init__(parent=parent) + self.setWidget(QLabel("Hier kommen die nicht installierten Spiele")) + + +class UpdateList(QWidget): + def __init__(self, parent): + super(UpdateList, self).__init__(parent=parent) + self.layout = QVBoxLayout() + self.layout.addWidget(QLabel("Updates")) + self.setLayout(self.layout) diff --git a/Rare/__init__.py b/Rare/__init__.py new file mode 100644 index 00000000..15cf400d --- /dev/null +++ b/Rare/__init__.py @@ -0,0 +1 @@ +__version__="0.0.1" \ No newline at end of file diff --git a/Rare/utils/__init__.py b/Rare/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Rare/utils/legendary.py b/Rare/utils/legendary.py new file mode 100644 index 00000000..d34d541a --- /dev/null +++ b/Rare/utils/legendary.py @@ -0,0 +1,40 @@ +import subprocess + +from legendary.cli import LegendaryCLI +from legendary.core import LegendaryCore + +core = LegendaryCore() +cli = LegendaryCLI + + +def get_installed(): + return core.get_installed_list() + # games = sorted(get_installed, key=lambda x: x.title) + + +def get_installed_names(): + names = [] + for i in core.get_installed_list(): + names.append(i.app_name) + return names + + +# return (games, dlcs) +def get_games_and_dlcs(): + if not core.login(): + print("Login Failed") + exit(1) + print('Getting game list... (this may take a while)') + return core.get_game_and_dlc_list() + + +def get_games(): + if not core.login(): + print("Login Failed") + exit(1) + print('Getting game list... (this may take a while)') + return core.get_game_list() + + +def start(game_name: str, args): + subprocess.run(["legendary", "launch", game_name]) \ No newline at end of file