diff --git a/rare/app.py b/rare/app.py index 4bcceecf..7dcb39e2 100644 --- a/rare/app.py +++ b/rare/app.py @@ -1,15 +1,13 @@ import configparser import logging import os -import platform import sys import time import traceback -import qtawesome from PyQt5.QtCore import QThreadPool, QSettings, QTranslator -from PyQt5.QtGui import QIcon, QPalette -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QStyleFactory, QMessageBox +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox from requests import HTTPError import rare.shared as shared @@ -17,7 +15,7 @@ from rare import languages_path, resources_path, cache_dir from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.main_window import MainWindow from rare.components.tray_icon import TrayIcon -from rare.utils.utils import load_color_scheme +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 file_name = os.path.join(cache_dir, f"logs/Rare_{start_time}.log") @@ -113,27 +111,20 @@ class App(QApplication): self.installTranslator(self.qt_translator) # Style + # lk: this is a bit silly but serves well until we have a class + # lk: store the default qt style name from the system on startup as a property for later reference + self.setProperty('rareDefaultQtStyle', self.style().objectName()) + if self.settings.value("color_scheme", None) is None and self.settings.value("style_sheet", None) is None: self.settings.setValue("color_scheme", "") self.settings.setValue("style_sheet", "RareStyle") - if color := self.settings.value("color_scheme", False): - self.setStyle(QStyleFactory.create("Fusion")) + if color_scheme := self.settings.value("color_scheme", False): self.settings.setValue("style_sheet", "") - custom_palette = load_color_scheme(os.path.join(resources_path, "colors", color + ".scheme")) - if custom_palette is not None: - self.setPalette(custom_palette) - qtawesome.set_defaults(color=custom_palette.color(QPalette.Text)) - - elif style := self.settings.value("style_sheet", False): - self.setStyle(QStyleFactory.create("Fusion")) + set_color_pallete(color_scheme) + elif style_sheet := self.settings.value("style_sheet", False): self.settings.setValue("color_scheme", "") - stylesheet = open(os.path.join(resources_path, "stylesheets", style, "stylesheet.qss")).read() - style_resource_path = os.path.join(resources_path, "stylesheets", style, "") - if platform.system() == "Windows": - style_resource_path = style_resource_path.replace('\\', '/') - self.setStyleSheet(stylesheet.replace("@path@", style_resource_path)) - qtawesome.set_defaults(color="white") + set_style_sheet(style_sheet) self.setWindowIcon(QIcon(os.path.join(resources_path, "images", "Rare.png"))) # launch app diff --git a/rare/components/main_window.py b/rare/components/main_window.py index 5d45c64d..120f7a9f 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -1,8 +1,8 @@ import os from logging import getLogger -from PyQt5.QtCore import Qt, QSettings, QTimer -from PyQt5.QtGui import QCloseEvent +from PyQt5.QtCore import Qt, QSettings, QTimer, QSize, QRect +from PyQt5.QtGui import QCloseEvent, QCursor from PyQt5.QtWidgets import QMainWindow, QMessageBox, QApplication from rare import data_dir, shared @@ -21,18 +21,34 @@ class MainWindow(QMainWindow): self.core = shared.core self.signals = shared.signals - self.offline = shared.args.offline - width, height = 1200, 800 - if self.settings.value("save_size", False): - width, height = self.settings.value("window_size", (1200, 800), tuple) - - desktop = QApplication.desktop() - self.setGeometry((desktop.width() - width) // 2, (desktop.height() - height) // 2, int(width), int(height)) self.setWindowTitle("Rare - GUI for legendary") self.tab_widget = TabWidget(self) self.setCentralWidget(self.tab_widget) + + width, height = 1200, 800 + if self.settings.value("save_size", False): + width, height = self.settings.value("window_size", (width, height), tuple) + + # move the window outside the viewport + self.move(-50000, -50000) + # show the window in order for it to get decorated, otherwise windowHandle() is null + self.show() + # get the margins of the decorated window + margins = self.windowHandle().frameMargins() + # hide the window again because we don't want to show it at this point + self.hide() + # get the screen the cursor is on + current_screen = QApplication.screenAt(QCursor.pos()) + # get the available screen geometry (excludes panels/docks) + screen_rect = current_screen.availableGeometry() + decor_width = margins.left() + margins.right() + decor_height = margins.top() + margins.bottom() + window_size = QSize(width, height).boundedTo(screen_rect.size() - QSize(decor_width, decor_height)) + self.resize(window_size) + self.move(screen_rect.center() - self.rect().adjusted(0, 0, decor_width, decor_height).center()) + if not shared.args.offline: self.rpc = DiscordRPC() self.tab_widget.delete_presence.connect(self.rpc.set_discord_rpc) @@ -60,7 +76,6 @@ class MainWindow(QMainWindow): self.tab_widget.games_tab.game_utils.prepare_launch(game, offline=shared.args.offline) else: logger.info(f"Could not find {game} in Games") - elif action.startswith("start"): self.show() os.remove(file_path) diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py index ad4f925e..cd1e22b2 100644 --- a/rare/components/tabs/__init__.py +++ b/rare/components/tabs/__init__.py @@ -9,7 +9,7 @@ from rare.components.tabs.games import GamesTab from rare.components.tabs.settings import SettingsTab from rare.components.tabs.settings.debug import DebugSettings from rare.components.tabs.shop import Shop -from rare.components.tabs.tab_utils import TabBar, TabButtonWidget +from rare.components.tabs.tab_utils import MainTabBar, TabButtonWidget class TabWidget(QTabWidget): @@ -20,7 +20,10 @@ class TabWidget(QTabWidget): disabled_tab = 3 if not shared.args.offline else 1 self.core = shared.core self.signals = shared.signals - self.setTabBar(TabBar(disabled_tab)) + self.setTabBar(MainTabBar(disabled_tab)) + # lk: Figure out why this adds a white line at the top + # lk: despite setting qproperty-drawBase to 0 in the stylesheet + # self.setDocumentMode(True) # Generate Tabs self.games_tab = GamesTab() self.addTab(self.games_tab, self.tr("Games")) diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py index 7eed6f5c..8800edb2 100644 --- a/rare/components/tabs/games/game_info/game_settings.py +++ b/rare/components/tabs/games/game_info/game_settings.py @@ -4,7 +4,7 @@ from logging import getLogger from typing import Tuple from PyQt5.QtCore import QSettings, QThreadPool -from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QLabel, QVBoxLayout, QPushButton +from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QLabel, QPushButton, QSizePolicy from qtawesome import icon from legendary.core import LegendaryCore @@ -54,19 +54,16 @@ class GameSettings(QWidget, Ui_GameSettings): self.core = core self.settings = QSettings() - save_widget = QWidget() - save_widget.setLayout(QVBoxLayout()) self.cloud_save_path_edit = PathEdit("", file_type=QFileDialog.DirectoryOnly, ph_text=self.tr("Cloud save path"), edit_func=lambda text: (os.path.exists(text), text), save_func=self.save_save_path) - save_widget.layout().addWidget(self.cloud_save_path_edit) + self.cloud_gb.layout().addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit) self.compute_save_path_button = QPushButton(icon("fa.magic"), self.tr("Auto compute save path")) - save_widget.layout().addWidget(self.compute_save_path_button) + self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) self.compute_save_path_button.clicked.connect(self.compute_save_path) - - self.cloud_gb.layout().addRow(QLabel(self.tr("Save path")), save_widget) + self.cloud_gb.layout().addRow(None, self.compute_save_path_button) self.offline.currentIndexChanged.connect( lambda x: self.update_combobox(x, "offline") @@ -102,6 +99,13 @@ class GameSettings(QWidget, Ui_GameSettings): self.proton_prefix_layout.addWidget(self.proton_prefix) self.linux_settings = LinuxAppSettings() + # FIXME: Remove the spacerItem from the linux settings + # FIXME: This should be handled differently at soem point in the future + for item in [self.linux_settings.layout().itemAt(idx) for idx in range(self.linux_settings.layout().count())]: + if item.spacerItem(): + self.linux_settings.layout().removeItem(item) + del item + # FIXME: End of FIXME self.linux_layout.addWidget(self.linux_settings) else: self.proton_groupbox.setVisible(False) diff --git a/rare/components/tabs/games/import_sync/egl_sync_group.py b/rare/components/tabs/games/import_sync/egl_sync_group.py index 7509d2ff..165f6bea 100644 --- a/rare/components/tabs/games/import_sync/egl_sync_group.py +++ b/rare/components/tabs/games/import_sync/egl_sync_group.py @@ -23,43 +23,33 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): self.setupUi(self) self.egl_path_info.setProperty('infoLabel', 1) - self.core = shared.core - self.threadpool = QThreadPool.globalInstance() - if not self.core.egl.programdata_path: - if platform.system() == 'Windows': - self.egl_path_info.setText(os.path.expandvars(PathSpec.egl_programdata)) - else: - self.egl_path_info.setText(self.tr('Updating...')) - wine_resolver = WineResolver(PathSpec.egl_programdata, 'default', shared.core) - wine_resolver.signals.result_ready.connect(self.wine_resolver_cb) - self.threadpool.start(wine_resolver) + self.thread_pool = QThreadPool.globalInstance() - else: - self.egl_path_info.setText(self.core.egl.programdata_path) - - egl_path = shared.core.egl.programdata_path - if egl_path is None: - egl_path = shared.core.lgd.config.get( - "default", - "wine_prefix", - fallback=PathSpec().wine_egl_prefixes(results=1)) - - self.egl_path_edit = PathEdit( - path=egl_path, - ph_text=self.tr('Path to the Wine prefix where EGL is installed, or the Manifests folder'), - file_type=QFileDialog.DirectoryOnly, - edit_func=self.egl_path_edit_cb, - save_func=self.egl_path_save_cb, - parent=self - ) - self.egl_path_edit.textChanged.connect(self.egl_path_changed) - self.egl_path_layout.addWidget(self.egl_path_edit) - - if platform.system() == "Windows": - self.egl_path_label.setVisible(False) + if platform.system() == 'Windows': + self.egl_path_edit_label.setVisible(False) self.egl_path_edit.setVisible(False) self.egl_path_info_label.setVisible(False) self.egl_path_info.setVisible(False) + else: + self.egl_path_edit = PathEdit( + path=shared.core.egl.programdata_path, + ph_text=self.tr('Path to the Wine prefix where EGL is installed, or the Manifests folder'), + file_type=QFileDialog.DirectoryOnly, + edit_func=self.egl_path_edit_edit_cb, + save_func=self.egl_path_edit_save_cb, + parent=self + ) + self.egl_path_edit.textChanged.connect(self.egl_path_changed) + self.egl_path_edit_layout.addWidget(self.egl_path_edit) + + if not shared.core.egl.programdata_path: + self.egl_path_info.setText(self.tr('Updating...')) + wine_resolver = WineResolver(PathSpec.egl_programdata, 'default', shared.core) + wine_resolver.signals.result_ready.connect(self.wine_resolver_cb) + self.thread_pool.start(wine_resolver) + else: + self.egl_path_info_label.setVisible(False) + self.egl_path_info.setVisible(False) self.egl_sync_check.setChecked(shared.core.egl_sync_enabled) self.egl_sync_check.stateChanged.connect(self.egl_sync_changed) @@ -76,30 +66,33 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): def wine_resolver_cb(self, path): self.egl_path_info.setText(path) - if path: - self.egl_path_edit.setText(path) - else: + if not path: self.egl_path_info.setText( self.tr('Default Wine prefix is unset, or path does not exist. ' - 'Create it or configure it in Settings -> Linux')) + 'Create it or configure it in Settings -> Linux.')) + elif not os.path.exists(path): + self.egl_path_info.setText( + self.tr('Default Wine prefix is set but EGL manifests path does not exist. ' + 'Your configured default Wine prefix might not be where EGL is installed.')) + else: + self.egl_path_edit.setText(path) @staticmethod - def egl_path_edit_cb(path) -> Tuple[bool, str]: + def egl_path_edit_edit_cb(path) -> Tuple[bool, str]: if not path: return True, path - if platform.system() != "Windows": - if os.path.exists(os.path.join(path, 'system.reg')) and os.path.exists(os.path.join(path, 'dosdevices/c:')): - # path is a wine prefix - path = os.path.join(path, 'dosdevices/c:', 'ProgramData/Epic/EpicGamesLauncher/Data/Manifests') - elif not path.rstrip('/').endswith('ProgramData/Epic/EpicGamesLauncher/Data/Manifests'): - # lower() might or might not be needed in the check - return False, path + if os.path.exists(os.path.join(path, 'system.reg')) and os.path.exists(os.path.join(path, 'dosdevices/c:')): + # path is a wine prefix + path = os.path.join(path, 'dosdevices/c:', 'ProgramData/Epic/EpicGamesLauncher/Data/Manifests') + elif not path.rstrip('/').endswith('ProgramData/Epic/EpicGamesLauncher/Data/Manifests'): + # lower() might or might not be needed in the check + return False, path if os.path.exists(path): return True, path return False, path @staticmethod - def egl_path_save_cb(path): + def egl_path_edit_save_cb(path): if not path or not os.path.exists(path): # This is the same as "--unlink" shared.core.egl.programdata_path = None @@ -127,25 +120,25 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): if state == Qt.Unchecked: self.import_list.setEnabled(bool(self.import_list.items)) self.export_list.setEnabled(bool(self.export_list.items)) - self.core.lgd.config.remove_option('Legendary', 'egl_sync') + shared.core.lgd.config.remove_option('Legendary', 'egl_sync') else: - self.core.lgd.config.set('Legendary', 'egl_sync', str(True)) + shared.core.lgd.config.set('Legendary', 'egl_sync', str(True)) # lk: do import/export here since automatic sync was selected self.import_list.mark(Qt.Checked) self.export_list.mark(Qt.Checked) sync_worker = EGLSyncWorker(self.import_list, self.export_list) - self.threadpool.start(sync_worker) + self.thread_pool.start(sync_worker) self.import_list.setEnabled(False) self.export_list.setEnabled(False) # self.update_lists() - self.core.lgd.save_config() + shared.core.lgd.save_config() def update_lists(self): # self.egl_watcher.blockSignals(True) if have_path := bool(shared.core.egl.programdata_path) and self.egl_path_edit.is_valid: # NOTE: need to clear known manifests to force refresh shared.core.egl.manifests.clear() - self.egl_sync_label.setEnabled(have_path) + self.egl_sync_check_label.setEnabled(have_path) self.egl_sync_check.setEnabled(have_path) self.import_list.populate(have_path) self.import_list.setEnabled(have_path) diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py index b601a3de..7720c58f 100644 --- a/rare/components/tabs/settings/rare.py +++ b/rare/components/tabs/settings/rare.py @@ -11,7 +11,7 @@ from rare import cache_dir, shared from rare.components.tabs.settings.rpc import RPCSettings from rare.ui.components.tabs.settings.rare import Ui_RareSettings from rare.utils import utils -from rare.utils.utils import get_translations, get_color_schemes, get_style_sheets +from rare.utils.utils import get_translations, get_color_schemes, set_color_pallete, get_style_sheets, set_style_sheet logger = getLogger("RareSettings") @@ -163,9 +163,11 @@ class RareSettings(QWidget, Ui_RareSettings): self.style_select.setCurrentIndex(0) self.style_select.setDisabled(True) self.settings.setValue("color_scheme", self.color_select.currentText()) + set_color_pallete(self.color_select.currentText()) else: self.settings.setValue("color_scheme", "") self.style_select.setDisabled(False) + set_color_pallete("") self.interface_info.setVisible(True) def on_style_select_changed(self, style): @@ -173,9 +175,11 @@ class RareSettings(QWidget, Ui_RareSettings): self.color_select.setCurrentIndex(0) self.color_select.setDisabled(True) self.settings.setValue("style_sheet", self.style_select.currentText()) + set_style_sheet(self.style_select.currentText()) else: self.settings.setValue("style_sheet", "") self.color_select.setDisabled(False) + set_style_sheet("") self.interface_info.setVisible(True) def open_dir(self): diff --git a/rare/components/tabs/tab_utils.py b/rare/components/tabs/tab_utils.py index e1597467..64b70e72 100644 --- a/rare/components/tabs/tab_utils.py +++ b/rare/components/tabs/tab_utils.py @@ -4,20 +4,20 @@ from PyQt5.QtWidgets import QTabBar, QToolButton from qtawesome import icon -class TabBar(QTabBar): +class MainTabBar(QTabBar): def __init__(self, expanded): - super(TabBar, self).__init__() + super(MainTabBar, self).__init__() self._expanded = expanded - self.setObjectName("main_tab_bar") + self.setObjectName("MainTabBar") self.setFont(QFont("Arial", 13)) # self.setContentsMargins(0,10,0,10) def tabSizeHint(self, index): - size = super(TabBar, self).tabSizeHint(index) + size = super(MainTabBar, self).tabSizeHint(index) if index == self._expanded: offset = self.width() for index in range(self.count()): - offset -= super(TabBar, self).tabSizeHint(index).width() + offset -= super(MainTabBar, self).tabSizeHint(index).width() size.setWidth(max(size.width(), size.width() + offset)) return size diff --git a/rare/resources/stylesheets/RareStyle/stylesheet.qss b/rare/resources/stylesheets/RareStyle/stylesheet.qss index 07dbb465..02ff1230 100644 --- a/rare/resources/stylesheets/RareStyle/stylesheet.qss +++ b/rare/resources/stylesheets/RareStyle/stylesheet.qss @@ -81,16 +81,18 @@ QSpinBox, QDoubleSpinBox, QProgressBar, QPushButton { - height: 3ex; + height: 1.30em; } QToolButton { - height: 2.5ex; + height: 1.10em; } QFrame[frameShape="6"] { border-radius: 4px; } +QFrame[noBorder="1"], QListView[noBorder="1"], -QScrollArea[noBorder="1"] { +QScrollArea[noBorder="1"], +QStackedWidget[noBorder="1"] { border-color: transparent; } QComboBox { @@ -138,7 +140,7 @@ QComboBox QAbstractItemView { *::drop-down, *::drop-down:editable { width: 14px; - image: url(@path@drop-down.svg); + image: url("@path@drop-down.svg"); } *::up-button, *::down-button { @@ -147,12 +149,12 @@ QComboBox QAbstractItemView { *::up-button { subcontrol-position: top right; /* position at the top right corner */ border-bottom-width: 1; - image: url(@path@sort-up.svg); + image: url("@path@sort-up.svg"); } *::down-button { subcontrol-position: bottom right; /* position at bottom right corner */ border-top-width: 1; - image: url(@path@sort-down.svg); + image: url("@path@sort-down.svg"); } QProgressBar { text-align: center; @@ -234,7 +236,8 @@ QListView::item, QTreeView::item { margin-right: 1px; } -/* The first element is attaching to the QHeaderView +/* The first element is attaching to the QHeaderView */ +/* QTableView[currentColumn="0"]::item { margin-left: 1px; } @@ -294,7 +297,7 @@ QPushButton#menu_button { width: 18px; height: 18px; } -QPushButton:hover#menu_button { +QPushButton#menu_button:hover { background-color: "#334"; } QPushButton[install="1"], @@ -302,11 +305,11 @@ QPushButton#install_button { background-color: "#090"; } QPushButton[install="1"]:hover, -QPushButton:hover#install_button { +QPushButton#install_button:hover { background-color: "#060"; } QPushButton[install="1"]:disabled, -QPushButton:disabled#install_button { +QPushButton#install_button:disabled { background-color: "#020"; } QPushButton[uninstall="1"], @@ -314,17 +317,17 @@ QPushButton#uninstall_button { background-color: "#900"; } QPushButton[uninstall="1"]:hover, -QPushButton:hover#uninstall_button { +QPushButton#uninstall_button:hover { background-color: "#600"; } QPushButton[uninstall="1"]:disabled, -QPushButton:disabled#uninstall_button { +QPushButton#uninstall_button:disabled { background-color: "#200"; } QPushButton#success{ background-color: "lime"; } -QPushButton:hover#installed_menu_button { +QPushButton#installed_menu_button:hover { background-color: green; } @@ -376,7 +379,7 @@ QListView::indicator:checked, QTreeView::indicator:checked, QTableView::indicator:checked { border-radius: 2px; - image: url(@path@square.svg); + image: url("@path@square.svg"); } QGroupBox::indicator:indeterminate, QCheckBox::indicator:indeterminate, @@ -384,40 +387,40 @@ QListView::indicator:indeterminate, QTreeView::indicator:indeterminate, QTableView::indicator:indeterminate { border-radius: 2px; - image: url(@path@half-square.svg); + image: url("@path@half-square.svg"); } QRadioButton::indicator:checked { border-radius: 5%; - image: url(@path@circle.svg); + image: url("@path@circle.svg"); } QGroupBox::indicator:checked:disabled, QCheckBox::indicator:checked:disabled, QListView::indicator:checked:disabled, QTreeView::indicator:checked:disabled, QTableView::indicator:checked:disabled { - image: url(@path@square-disabled.svg); + image: url("@path@square-disabled.svg"); } QGroupBox::indicator:indeterminate:disabled, QCheckBox::indicator:indeterminate:disabled, QListView::indicator:indeterminate:disabled, QTreeView::indicator:indeterminate:disabled, QTableView::indicator:indeterminate:disabled { - image: url(@path@half-square-disabled.svg); + image: url("@path@half-square-disabled.svg"); } QRadioButton::indicator:checked:disabled { - image: url(@path@circle-disabled.svg); + image: url("@path@circle-disabled.svg"); } QGroupBox, QGroupBox#group, QGroupBox#settings_widget { font-weight: bold; - margin-top: 1ex; + margin-top: 0.5em; /* margin-left: 0.5em; /* Offset to the left */ border-width: 1px; border-style: solid; border-radius: 4px; - padding-top: 3ex; + padding-top: 1.2em; } QGroupBox#game_widget_icon { margin: 2px; @@ -466,7 +469,7 @@ QTabBar { qproperty-drawBase: 0; } QTabBar::tab { - margin: 3px; + margin: 0px; border-width: 1px; border-style: solid; border-color: transparent; @@ -477,75 +480,189 @@ QTabBar::tab:bottom { padding-left: 12px; padding-right: 12px; } +QTabBar::tab:top:hover, +QTabBar::tab:bottom:hover { + border-color: #483d8b; + border-left-color: transparent; + border-right-color: transparent; +} QTabBar::tab:top { border-top-width: 3px; border-top-color: #3c3f41; + border-bottom-color: #483d8b; + background: qlineargradient( + x1: 0, y1: -1, x2: 0, y2: 1, stop: 0 #3c3f41, stop: 1 #202225); } QTabBar::tab:bottom { border-bottom-width: 3px; + border-top-color: #483d8b; + border-bottom-color: #3c3f41; + background: qlineargradient( + x1: 0, y1: 2, x2: 0, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); +} +QTabBar::tab:top:hover { + background: qlineargradient( + x1: 0, y1: -1, x2: 0, y2: 1, stop: 0 #483d8b, stop: 1 #202225); +} +QTabBar::tab:bottom:hover { + background: qlineargradient( + x1: 0, y1: 2, x2: 0, y2: 0, stop: 0 #483d8b, stop: 1 #202225); +} +QTabBar::tab:top:selected { + border-color: #483d8b; + border-bottom-color: transparent; + background: #202225; +} +QTabBar::tab:bottom:selected { + border-color: #483d8b; + border-top-color: transparent; + background: #202225; +} +QTabBar::tab:top:disabled { border-bottom-color: #3c3f41; } -QTabBar::tab:hover:top, -QTabBar::tab:hover:bottom { - border-color: #483d8b; +QTabBar::tab:bottom:disabled { + border-top-color: #3c3f41; } -QTabBar::tab:hover:top, -QTabBar::tab:selected:top { - border-top-color: #483d8b; - background: qlineargradient(x1: 0, y1: -1, x2: 0, y2: 1, - stop: 0 #483d8b, stop: 1 #202225); /* stop: 0 #28224D */ +QTabBar::tab:top:selected:disabled { + border-color: #3c3f41; + border-bottom-color: transparent; } -QTabBar::tab:hover:bottom, -QTabBar::tab:selected:bottom { - border-bottom-color: #483d8b; - background: qlineargradient(x1: 0, y1: 2, x2: 0, y2: 0, - stop: 0 #483d8b, stop: 1 #202225); /* stop: 0 #28224D */ -} -QTabBar::tab:top#main_tab_bar { - border-color: transparent; - padding: 5px; -} -QTabBar::tab:hover:top#main_tab_bar { - border-color: #483d8b; -} -QTabBar::tab:selected:top#main_tab_bar { - border-top-color: #483d8b; +QTabBar::tab:bottom:selected:disabled { + border-color: #3c3f41; + border-top-color: transparent; } QTabBar::tab:left, QTabBar::tab:right { padding-top: 2px; padding-bottom: 2px; } +QTabBar::tab:left:hover, +QTabBar::tab:right:hover { + border-color: #483d8b; + border-top-color: transparent; + border-bottom-color: transparent; +} QTabBar::tab:left { border-left-width: 3px; border-left-color: #3c3f41; + border-right-color: #483d8b; + background: qlineargradient( + x1: -1, y1: 0, x2: 1, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); } QTabBar::tab:right { border-right-width: 3px; border-right-color: #3c3f41; + border-left-color: #483d8b; + background: qlineargradient( + x1: 2, y1: 0, x2: 0, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); } -QTabBar::tab:hover:left, -QTabBar::tab:hover:right { - border-color: #483d8b; /* #2f4f4f */ +QTabBar::tab:left:hover { + background: qlineargradient( + x1: -1, y1: 0, x2: 1, y2: 0, stop: 0 #483d8b, stop: 1 #202225); } -QTabBar::tab:hover:left, -QTabBar::tab:selected:left { - border-left-color: #483d8b; /* #2f4f4f */ - background: qlineargradient(x1: -1, y1: 0, x2: 1, y2: 0, - stop: 0 #483d8b, stop: 1 #202225); /* stop: 0 #2f4f4f stop: 0.2 #203636 */ +QTabBar::tab:right:hover { + background: qlineargradient( + x1: 2, y1: 0, x2: 0, y2: 0, stop: 0 #483d8b, stop: 1 #202225); } -QTabBar::tab:hover:right, -QTabBar::tab:selected:right { - border-right-color: #483d8b; /* #2f4f4f */ - background: qlineargradient(x1: 2, y1: 0, x2: 0, y2: 0, - stop: 0 #483d8b, stop: 1 #202225); /* stop: 0 #2f4f4f stop: 0.2 #203636 */ +QTabBar::tab:left:selected { + border-color: #483d8b; + border-right-color: transparent; + background: #202225; } -QTabBar::tab:disabled#SideTabBar { +QTabBar::tab:right:selected { + border-color: #483d8b; + border-left-color: transparent; + background: #202225; +} +QTabBar::tab:left:disabled { + border-right-color: #3c3f41; +} +QTabBar::tab:right:disabled { + border-left-color: #3c3f41; +} +QTabBar::tab:left:selected:disabled { + border-color: #3c3f41; + border-right-color: transparent; +} +QTabBar::tab:right:selected:disabled { + border-color: #3c3f41; + border-left-color: transparent; +} + +QTabBar#MainTabBar { + border-width: 1px; + border-style: solid; + border-color: transparent; + border-bottom-color: #483d8b; + /* + background: qlineargradient( + x1: 0, y1: -3, x2: 0, y2: 1, stop: 0 #3c3f41, stop: 1 #202225); + */ +} +QTabBar#MainTabBar:disabled { + border-color: transparent; + border-bottom-color: #3c3f41; +} +QTabBar#MainTabBar::tab { + margin-left: 3px; + margin-right: 3px; + border-top-color: transparent; + border-bottom-color: #483d8b; + padding: 5px; +}/* +QTabBar#MainTabBar::tab:top:first, +QTabBar#MainTabBar::tab:bottom:first { + margin-left: 0px; + border-left: transparent; +} +QTabBar#MainTabBar::tab:top:last, +QTabBar#MainTabBar::tab:bottom:last { + margin-right: 0px; + border-right: transparent; +}*/ +QTabBar#MainTabBar::tab:top:hover { + border-top-color: #483d8b; +} +QTabBar#MainTabBar::tab:top:selected { + border-color: #483d8b; + border-bottom-color: #202225; +} +QTabBar#SideTabBar { + border-width: 1px; + border-style: solid; + border-color: transparent; + border-right-color: #483d8b; + /* + background: qlineargradient( + x1: -3, y1: 0, x2: 1, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + */ +} +QTabBar#SideTabBar:disabled { + border-color: transparent; + border-right-color: #3c3f41; +} +QTabBar#SideTabBar::tab { + margin-top: 3px; + margin-bottom: 3px; +}/* +QTabBar#SideTabBar::tab:left:first, +QTabBar#SideTabBar::tab:right:first { + margin-top: 0px; + border-top: transparent; +} +QTabBar#SideTabBar::tab:left:last, +QTabBar#SideTabBar::tab:right:last { + margin-bottom: 0px; + border-bottom: transparent; +}*/ +QTabBar#SideTabBar::tab:disabled { color: transparent; border-color: transparent; background-color: transparent; } + QToolTip { border-width: 1px; border-style: solid; diff --git a/rare/ui/components/tabs/games/game_info/game_settings.py b/rare/ui/components/tabs/games/game_info/game_settings.py index ecd7d813..438fb1a5 100644 --- a/rare/ui/components/tabs/games/game_info/game_settings.py +++ b/rare/ui/components/tabs/games/game_info/game_settings.py @@ -2,13 +2,13 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/game_settings.ui' # -# Created by: PyQt5 UI code generator 5.15.5 +# Created by: PyQt5 UI code generator 5.15.6 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_GameSettings(object): @@ -88,6 +88,19 @@ class Ui_GameSettings(object): self.wrapper_layout.addWidget(self.wrapper_button) self.launch_settings_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.wrapper_widget) self.game_settings_layout.addWidget(self.launch_settings_groupbox, 0, QtCore.Qt.AlignTop) + self.cloud_gb = QtWidgets.QGroupBox(GameSettings) + self.cloud_gb.setObjectName("cloud_gb") + self.cloud_saves_layout = QtWidgets.QFormLayout(self.cloud_gb) + self.cloud_saves_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.cloud_saves_layout.setObjectName("cloud_saves_layout") + self.cloud_sync_label = QtWidgets.QLabel(self.cloud_gb) + self.cloud_sync_label.setObjectName("cloud_sync_label") + self.cloud_saves_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.cloud_sync_label) + self.cloud_sync = QtWidgets.QCheckBox(self.cloud_gb) + self.cloud_sync.setText("") + self.cloud_sync.setObjectName("cloud_sync") + self.cloud_saves_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cloud_sync) + self.game_settings_layout.addWidget(self.cloud_gb) self.proton_groupbox = QtWidgets.QGroupBox(GameSettings) self.proton_groupbox.setObjectName("proton_groupbox") self.proton_layout = QtWidgets.QFormLayout(self.proton_groupbox) @@ -116,18 +129,6 @@ class Ui_GameSettings(object): self.linux_layout.setObjectName("linux_layout") self.proton_layout.setLayout(3, QtWidgets.QFormLayout.SpanningRole, self.linux_layout) self.game_settings_layout.addWidget(self.proton_groupbox, 0, QtCore.Qt.AlignTop) - self.cloud_gb = QtWidgets.QGroupBox(GameSettings) - self.cloud_gb.setObjectName("cloud_gb") - self.formLayout = QtWidgets.QFormLayout(self.cloud_gb) - self.formLayout.setObjectName("formLayout") - self.cloud_sync_label = QtWidgets.QLabel(self.cloud_gb) - self.cloud_sync_label.setObjectName("cloud_sync_label") - self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.cloud_sync_label) - self.cloud_sync = QtWidgets.QCheckBox(self.cloud_gb) - self.cloud_sync.setText("") - self.cloud_sync.setObjectName("cloud_sync") - self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cloud_sync) - self.game_settings_layout.addWidget(self.cloud_gb) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.game_settings_layout.addItem(spacerItem) @@ -152,12 +153,12 @@ class Ui_GameSettings(object): self.wrapper_label.setText(_translate("GameSettings", "Wrapper")) self.wrapper.setPlaceholderText(_translate("GameSettings", "e.g. optirun")) self.wrapper_button.setText(_translate("GameSettings", "Save")) + self.cloud_gb.setTitle(_translate("GameSettings", "Cloud Saves")) + self.cloud_sync_label.setText(_translate("GameSettings", "Sync with cloud")) self.proton_groupbox.setTitle(_translate("GameSettings", "Linux Settings")) self.proton_wrapper_label.setText(_translate("GameSettings", "Proton")) self.proton_wrapper.setItemText(0, _translate("GameSettings", "Don\'t use Proton")) self.proton_prefix_label.setText(_translate("GameSettings", "Prefix")) - self.cloud_gb.setTitle(_translate("GameSettings", "Cloud Saves")) - self.cloud_sync_label.setText(_translate("GameSettings", "Sync with cloud")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/game_info/game_settings.ui b/rare/ui/components/tabs/games/game_info/game_settings.ui index 9962928d..41374075 100644 --- a/rare/ui/components/tabs/games/game_info/game_settings.ui +++ b/rare/ui/components/tabs/games/game_info/game_settings.ui @@ -105,16 +105,16 @@ - - - - 0 - - - 0 - - - 0 + + + + 0 + + + 0 + + + 0 0 @@ -142,24 +142,24 @@ - - - - Wrapper - - - - - - - - 0 - - - 0 - - - 0 + + + + Wrapper + + + + + + + + 0 + + + 0 + + + 0 0 @@ -190,6 +190,32 @@ + + + + Cloud Saves + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + Sync with cloud + + + + + + + + + + + + + @@ -232,51 +258,28 @@ - - - 0 - - + + + 0 + + - - - - Cloud Saves - - - - - - Sync with cloud - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/rare/ui/components/tabs/games/import_sync/egl_sync_group.py b/rare/ui/components/tabs/games/import_sync/egl_sync_group.py index 44da393d..c5f11ed1 100644 --- a/rare/ui/components/tabs/games/import_sync/egl_sync_group.py +++ b/rare/ui/components/tabs/games/import_sync/egl_sync_group.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/games/import_sync/egl_sync_group.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt5 UI code generator 5.15.6 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -21,12 +21,12 @@ class Ui_EGLSyncGroup(object): self.egl_sync_layout = QtWidgets.QFormLayout(EGLSyncGroup) self.egl_sync_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.egl_sync_layout.setObjectName("egl_sync_layout") - self.egl_path_label = QtWidgets.QLabel(EGLSyncGroup) - self.egl_path_label.setObjectName("egl_path_label") - self.egl_sync_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.egl_path_label) - self.egl_path_layout = QtWidgets.QHBoxLayout() - self.egl_path_layout.setObjectName("egl_path_layout") - self.egl_sync_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.egl_path_layout) + self.egl_path_edit_label = QtWidgets.QLabel(EGLSyncGroup) + self.egl_path_edit_label.setObjectName("egl_path_edit_label") + self.egl_sync_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.egl_path_edit_label) + self.egl_path_edit_layout = QtWidgets.QHBoxLayout() + self.egl_path_edit_layout.setObjectName("egl_path_edit_layout") + self.egl_sync_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.egl_path_edit_layout) self.egl_path_info_label = QtWidgets.QLabel(EGLSyncGroup) self.egl_path_info_label.setObjectName("egl_path_info_label") self.egl_sync_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.egl_path_info_label) @@ -42,9 +42,9 @@ class Ui_EGLSyncGroup(object): self.import_export_layout = QtWidgets.QVBoxLayout() self.import_export_layout.setObjectName("import_export_layout") self.egl_sync_layout.setLayout(3, QtWidgets.QFormLayout.SpanningRole, self.import_export_layout) - self.egl_sync_label = QtWidgets.QLabel(EGLSyncGroup) - self.egl_sync_label.setObjectName("egl_sync_label") - self.egl_sync_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.egl_sync_label) + self.egl_sync_check_label = QtWidgets.QLabel(EGLSyncGroup) + self.egl_sync_check_label.setObjectName("egl_sync_check_label") + self.egl_sync_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.egl_sync_check_label) self.retranslateUi(EGLSyncGroup) QtCore.QMetaObject.connectSlotsByName(EGLSyncGroup) @@ -52,9 +52,9 @@ class Ui_EGLSyncGroup(object): def retranslateUi(self, EGLSyncGroup): _translate = QtCore.QCoreApplication.translate EGLSyncGroup.setTitle(_translate("EGLSyncGroup", "Sync with Epic Games Launcher")) - self.egl_path_label.setText(_translate("EGLSyncGroup", "Prefix/Manifest path")) + self.egl_path_edit_label.setText(_translate("EGLSyncGroup", "Prefix/Manifest path")) self.egl_path_info_label.setText(_translate("EGLSyncGroup", "Estimated path")) - self.egl_sync_label.setText(_translate("EGLSyncGroup", "Enable automatic sync")) + self.egl_sync_check_label.setText(_translate("EGLSyncGroup", "Enable automatic sync")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/import_sync/egl_sync_group.ui b/rare/ui/components/tabs/games/import_sync/egl_sync_group.ui index f62c5c91..fb23434c 100644 --- a/rare/ui/components/tabs/games/import_sync/egl_sync_group.ui +++ b/rare/ui/components/tabs/games/import_sync/egl_sync_group.ui @@ -27,14 +27,14 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + Prefix/Manifest path - + @@ -64,7 +64,7 @@ - + Enable automatic sync diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index 710e6936..2b5a362b 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -338,6 +338,7 @@ class SideTabWidget(QTabWidget): def __init__(self, show_back: bool = False, parent=None): super(SideTabWidget, self).__init__(parent=parent) self.setTabBar(SideTabBar()) + self.setDocumentMode(True) self.setTabPosition(QTabWidget.West) if show_back: self.addTab(QWidget(), qta_icon("mdi.keyboard-backspace"), self.tr("Back")) diff --git a/rare/utils/utils.py b/rare/utils/utils.py index e4416452..fe01b82d 100644 --- a/rare/utils/utils.py +++ b/rare/utils/utils.py @@ -6,10 +6,12 @@ import shutil import subprocess import sys from logging import getLogger -from typing import Tuple +from typing import Tuple, List import requests +import qtawesome from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QRunnable, QSettings, Qt +from PyQt5.QtWidgets import QApplication, QStyleFactory, QStyle from PyQt5.QtGui import QPalette, QColor, QPixmap, QImage from requests import HTTPError @@ -116,7 +118,7 @@ color_group_map = { } -def load_color_scheme(path: str): +def load_color_scheme(path: str) -> QPalette: palette = QPalette() scheme = QSettings(path, QSettings.IniFormat) try: @@ -138,7 +140,20 @@ def load_color_scheme(path: str): return palette -def get_color_schemes(): +def set_color_pallete(color_scheme: str): + if not color_scheme: + QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle'))) + QApplication.instance().setStyleSheet("") + QApplication.instance().setPalette(QApplication.instance().style().standardPalette()) + return + QApplication.instance().setStyle(QStyleFactory.create("Fusion")) + custom_palette = load_color_scheme(os.path.join(resources_path, "colors", color_scheme + ".scheme")) + if custom_palette is not None: + QApplication.instance().setPalette(custom_palette) + qtawesome.set_defaults(color=custom_palette.color(QPalette.Text)) + + +def get_color_schemes() -> List[str]: colors = [] for file in os.listdir(os.path.join(resources_path, "colors")): if file.endswith(".scheme") and os.path.isfile(os.path.join(resources_path, "colors", file)): @@ -146,7 +161,21 @@ def get_color_schemes(): return colors -def get_style_sheets(): +def set_style_sheet(style_sheet: str): + if not style_sheet: + QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle'))) + QApplication.instance().setStyleSheet("") + return + QApplication.instance().setStyle(QStyleFactory.create("Fusion")) + stylesheet = open(os.path.join(resources_path, "stylesheets", style_sheet, "stylesheet.qss")).read() + style_resource_path = os.path.join(resources_path, "stylesheets", style_sheet, "") + if platform.system() == "Windows": + style_resource_path = style_resource_path.replace('\\', '/') + QApplication.instance().setStyleSheet(stylesheet.replace("@path@", style_resource_path)) + qtawesome.set_defaults(color="white") + + +def get_style_sheets() -> List[str]: styles = [] for folder in os.listdir(os.path.join(resources_path, "stylesheets")): if os.path.isfile(os.path.join(resources_path, "stylesheets", folder, "stylesheet.qss")): @@ -398,6 +427,10 @@ class WineResolver(QRunnable): # pylint: disable=E1136 self.signals.result_ready[str].emit(str()) return + if not os.path.exists(self.wine_binary) or not os.path.exists(self.winepath_binary): + # pylint: disable=E1136 + self.signals.result_ready[str].emit(str()) + return path = self.path.strip().replace('/', '\\') # lk: if path does not exist form cmd = [self.wine_binary, 'cmd', '/c', 'echo', path]