1
0
Fork 0
mirror of synced 2024-05-19 12:02:54 +12:00

Add SlidingStackedWidget from #196

This commit is contained in:
loathingKernel 2022-06-22 02:38:04 +03:00
parent dc9b8e6cb2
commit 5eb3ae7f80
17 changed files with 519 additions and 322 deletions

View file

@ -46,11 +46,6 @@ class ImageWorker(LaunchWorker):
na_dlc_list = [dlc[0] for dlc in na_dlcs.values()]
game_list = games + dlc_list + na_games + na_dlc_list
fetched = [False] * len(game_list)
def set_fetched(idx):
fetched[idx] = True
self.signals.progress.emit(sum(fetched))
for i, game in enumerate(game_list):
if game.app_title == "Unreal Engine":
@ -58,13 +53,6 @@ class ImageWorker(LaunchWorker):
self.core.lgd.set_game_meta(game.app_name, game)
self.image_manager.download_image_blocking(game)
self.signals.progress.emit(int(i / len(game_list) * 100))
# self.image_manager.download_image(
# game,
# load_callback=lambda: set_fetched(i),
# priority=i
# )
# while not all(fetched):
# continue
self.signals.progress.emit(100)

View file

@ -283,7 +283,7 @@ class UpdateWidget(QWidget):
+ self.game.version
+ " -> "
+ self.core.get_asset(
self.game.app_name, self.game.platform, True
self.game.app_name, self.game.platform, False
).build_version
)
)

View file

@ -1,8 +1,8 @@
from logging import getLogger
from typing import Tuple, Dict, Union, List
from PyQt5.QtCore import QSettings, QObjectCleanupHandler
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget
from PyQt5.QtCore import QSettings, Qt, pyqtSlot
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
from legendary.models.game import InstalledGame, Game
from rare.shared import (
@ -14,6 +14,7 @@ from rare.shared import (
from rare.shared.image_manager import ImageManagerSingleton
from rare.ui.components.tabs.games.games_tab import Ui_GamesTab
from rare.widgets.library_layout import LibraryLayout
from rare.widgets.sliding_stack import SlidingStackedWidget
from .cloud_save_utils import CloudSaveUtils
from .game_info import GameInfoTabs
from .game_info.uninstalled_info import UninstalledInfoTabs
@ -23,24 +24,26 @@ from .game_widgets.base_uninstalled_widget import BaseUninstalledWidget
from .game_widgets.installed_icon_widget import InstalledIconWidget
from .game_widgets.installed_list_widget import InstalledListWidget
from .game_widgets.installing_game_widget import InstallingGameWidget
from .game_widgets.uninstalled_icon_widget import IconWidgetUninstalled
from .game_widgets.uninstalled_list_widget import ListWidgetUninstalled
from .game_widgets.uninstalled_icon_widget import UninstalledIconWidget
from .game_widgets.uninstalled_list_widget import UninstalledListWidget
from .head_bar import GameListHeadBar
from .import_sync import ImportSyncTabs
logger = getLogger("GamesTab")
class GamesTab(QStackedWidget, Ui_GamesTab):
class GamesTab(QStackedWidget):
widgets: Dict[str, Tuple[
Union[InstalledIconWidget, IconWidgetUninstalled], Union[InstalledListWidget, ListWidgetUninstalled]]] = dict()
Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = dict()
running_games = list()
updates = set()
active_filter = 0
def __init__(self, parent=None):
super(GamesTab, self).__init__(parent=parent)
self.setupUi(self)
self.ui = Ui_GamesTab()
self.ui.setupUi(self)
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.args = ArgumentsSingleton()
@ -59,7 +62,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.head_bar = GameListHeadBar(self)
self.head_bar.import_clicked.connect(self.show_import)
self.head_bar.egl_sync_clicked.connect(self.show_egl_sync)
self.games.layout().insertWidget(0, self.head_bar)
self.ui.games.layout().insertWidget(0, self.head_bar)
self.game_info_tabs = GameInfoTabs(self.dlcs, self.game_utils, self)
self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0))
@ -101,13 +104,41 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.no_assets = []
self.installed = self.core.get_installed_list()
self.setup_game_list()
self.view_stack = SlidingStackedWidget(self.ui.library_frame)
self.view_stack.setFrameStyle(QFrame.NoFrame)
self.icon_view_scroll = QScrollArea(self.view_stack)
self.icon_view_scroll.setWidgetResizable(True)
self.icon_view_scroll.setFrameShape(QFrame.NoFrame)
self.icon_view_scroll.horizontalScrollBar().setDisabled(True)
self.list_view_scroll = QScrollArea(self.view_stack)
self.list_view_scroll.setWidgetResizable(True)
self.list_view_scroll.setFrameShape(QFrame.NoFrame)
self.list_view_scroll.horizontalScrollBar().setDisabled(True)
self.icon_view = QWidget(self.icon_view_scroll)
self.icon_view.setLayout(LibraryLayout(self.icon_view))
self.icon_view.layout().setContentsMargins(0, 0, 0, 0)
self.icon_view.layout().setAlignment(Qt.AlignTop)
self.list_view = QWidget(self.list_view_scroll)
self.list_view.setLayout(QVBoxLayout(self.list_view))
self.list_view.layout().setContentsMargins(3, 3, 9, 3)
self.list_view.layout().setAlignment(Qt.AlignTop)
self.icon_view_scroll.setWidget(self.icon_view)
self.list_view_scroll.setWidget(self.list_view)
self.view_stack.addWidget(self.icon_view_scroll)
self.view_stack.addWidget(self.list_view_scroll)
self.ui.library_frame_layout.addWidget(self.view_stack)
# add installing game widget for icon view: List view not supported
self.installing_widget = InstallingGameWidget()
self.icon_view.layout().addWidget(self.installing_widget)
self.installing_widget.setVisible(False)
if not self.settings.value("icon_view", True, bool):
self.scroll_widget.layout().insertWidget(1, self.list_view)
self.view_stack.setCurrentWidget(self.list_view_scroll)
self.head_bar.view.list()
else:
self.scroll_widget.layout().insertWidget(1, self.icon_view)
self.view_stack.setCurrentWidget(self.icon_view_scroll)
self.head_bar.search_bar.textChanged.connect(lambda x: self.filter_games("", x))
self.head_bar.filterChanged.connect(self.filter_games)
@ -118,7 +149,6 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
if f >= len(self.head_bar.available_filters):
f = 0
self.active_filter = self.head_bar.available_filters[f]
self.filter_games(self.active_filter)
# signals
self.signals.dl_progress.connect(self.installing_widget.set_status)
@ -131,7 +161,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.game_utils.update_list.connect(self.update_list)
self.game_list_scroll_area.horizontalScrollBar().setDisabled(True)
self.setup_game_list()
def installation_finished(self, app_name: str):
self.installing_widget.setVisible(False)
@ -178,19 +208,17 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.uninstalled_info_tabs.update_game(game)
self.setCurrentIndex(3)
@pyqtSlot()
def update_count_games_label(self):
self.ui.count_games_label.setText(
self.tr("Installed Games: {}\tAvailable Games: {}").format(
len(self.core.get_installed_list()), len(self.game_list)
)
)
def setup_game_list(self):
self.icon_view = QWidget()
self.icon_view.setLayout(LibraryLayout())
self.list_view = QWidget()
self.list_view.setLayout(QVBoxLayout())
self.update_count_games_label()
# add installing game widget for icon view: List view not supported
self.installing_widget = InstallingGameWidget()
self.icon_view.layout().addWidget(self.installing_widget)
self.installing_widget.setVisible(False)
# add installed games
for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title):
icon_widget, list_widget = self.add_installed_widget(igame.app_name)
@ -212,13 +240,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
def update_count_games_label(self):
self.count_games_label.setText(
self.tr("Installed Games: {} Available Games: {}").format(
len(self.core.get_installed_list()), len(self.game_list)
)
)
self.filter_games(self.active_filter)
def add_installed_widget(self, app_name):
pixmap = self.image_manager.get_pixmap(app_name)
@ -257,8 +279,8 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
elif self.ue_name:
pixmap = self.image_manager.get_pixmap(self.ue_name, color=False)
icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
list_widget = ListWidgetUninstalled(self.core, game, pixmap)
icon_widget = UninstalledIconWidget(game, self.core, pixmap)
list_widget = UninstalledListWidget(self.core, game, pixmap)
except Exception as e:
logger.error(f"{game.app_name} is broken. Don't add it to game list: {e}")
return None, None
@ -279,7 +301,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
if not filter_name and (t := self.active_filter):
filter_name = t
def get_visibility(widget):
def get_visibility(widget) -> Tuple[bool, float]:
app_name = widget.game.app_name
if not isinstance(widget,
@ -310,19 +332,50 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
visible = True
if (
search_text.lower() not in app_name.lower()
and search_text.lower() not in widget.game.app_title.lower()
search_text not in widget.game.app_name.lower()
and search_text not in widget.game.app_title.lower()
):
visible = False
return visible
opacity = 0.25
else:
opacity = 1.0
return visible, opacity
for t in self.widgets.values():
visible = get_visibility(t[0])
visible, opacity = get_visibility(t[0])
for w in t:
w.setVisible(visible)
w.image.setOpacity(opacity)
self.sort_list(search_text)
if self.installing_widget.game:
self.installing_widget.setVisible(get_visibility(self.installing_widget))
self.installing_widget.setVisible(get_visibility(self.installing_widget)[0])
@pyqtSlot()
def sort_list(self, sort_by: str = ""):
# lk: this is the existing sorting implemenation
# lk: it sorts by installed then by title
installing_widget = self.icon_view.layout().remove(type(self.installing_widget).__name__)
if sort_by:
self.icon_view.layout().sort(lambda x: (sort_by not in x.widget().game.app_title.lower(),))
else:
self.icon_view.layout().sort(
lambda x: (
not x.widget().is_installed,
x.widget().is_non_asset,
x.widget().app_title,
)
)
self.icon_view.layout().insert(0, installing_widget)
list_widgets = self.list_view.findChildren(InstalledListWidget) + self.list_view.findChildren(UninstalledListWidget)
if sort_by:
list_widgets.sort(key=lambda x: (sort_by not in x.game.app_title.lower(),))
else:
list_widgets.sort(
key=lambda x: (not x.is_installed, x.is_non_asset, x.app_title)
)
for idx, wl in enumerate(list_widgets):
self.list_view.layout().insertWidget(idx, wl)
def update_list(self, app_names: list = None):
logger.debug(f"Updating list for {app_names}")
@ -351,11 +404,15 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
widgets[0], BaseUninstalledWidget
):
logger.debug(f"Update Gamelist: New installed {app_name}")
self.icon_view.layout().removeWidget(self.widgets[app_name][0])
self.list_view.layout().removeWidget(self.widgets[app_name][1])
self.widgets[app_name][0].deleteLater()
self.widgets[app_name][1].deleteLater()
self.widgets.pop(app_name)
self.add_installed_widget(app_name)
icon_widget, list_widget = self.add_installed_widget(app_name)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
update_list = True
# uninstalled
@ -363,6 +420,8 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
widgets[0].game.app_name
) and isinstance(widgets[0], BaseInstalledWidget):
logger.debug(f"Update list: Uninstalled: {app_name}")
self.icon_view.layout().removeWidget(self.widgets[app_name][0])
self.list_view.layout().removeWidget(self.widgets[app_name][1])
self.widgets[app_name][0].deleteLater()
self.widgets[app_name][1].deleteLater()
@ -370,14 +429,16 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
game = self.core.get_game(app_name, False)
try:
self.add_uninstalled_widget(game)
icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
except Exception:
pass
update_list = True
# do not update, if only update finished
if update_list:
self._update_games()
self.sort_list()
else:
installed_names = [i.app_name for i in self.core.get_installed_list()]
@ -401,11 +462,15 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
if new_installed_games:
for name in new_installed_games:
self.icon_view.layout().removeWidget(self.widgets[name][0])
self.list_view.layout().removeWidget(self.widgets[name][1])
self.widgets[name][0].deleteLater()
self.widgets[name][1].deleteLater()
self.widgets.pop(name)
self.add_installed_widget(name)
icon_widget, list_widget = self.add_installed_widget(name)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
for name in new_uninstalled_games:
self.icon_view.layout().removeWidget(self.widgets[name][0])
@ -418,104 +483,19 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
game = self.core.get_game(name, False)
try:
self.add_uninstalled_widget(game)
icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_view.layout().addWidget(icon_widget)
self.list_view.layout().addWidget(list_widget)
except Exception:
pass
for igame in sorted(
self.core.get_installed_list(), key=lambda x: x.title
):
i_widget, list_widget = self.widgets[igame.app_name]
self.icon_view.layout().addWidget(i_widget)
self.list_view.layout().addWidget(list_widget)
self.installed = self.core.get_installed_list()
for game in self.no_assets:
i_widget, list_widget = self.widgets[game.app_name]
self.icon_view.layout().addWidget(i_widget)
self.list_view.layout().addWidget(list_widget)
# get Uninstalled games
self.uninstalled_games = []
games, self.dlcs = self.core.get_game_and_dlc_list()
for game in sorted(games, key=lambda x: x.app_title):
if (
not self.core.is_installed(game.app_name)
and game.app_name not in self.no_asset_names
):
i_widget, list_widget = self.widgets[game.app_name]
self.icon_view.layout().addWidget(i_widget)
self.list_view.layout().addWidget(list_widget)
self.uninstalled_games.append(game)
self.sort_list()
self.update_count_games_label()
def _update_games(self):
icon_layout = FlowLayout()
list_layout = QVBoxLayout()
icon_layout.addWidget(self.installing_widget)
for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title) + self.no_assets:
i_widget, l_widget = self.widgets.get(igame.app_name, (None, None))
if i_widget and l_widget:
icon_layout.addWidget(i_widget)
list_layout.addWidget(l_widget)
else:
logger.warning("Found installed game, without widget. Generating widget... ")
try:
i_widget, l_widget = self.add_installed_widget(igame.app_name)
icon_layout.addWidget(i_widget)
list_layout.addWidget(l_widget)
except Exception as e:
logger.error(str(e))
continue
# get Uninstalled games
self.game_list, self.dlcs = self.core.get_game_and_dlc_list(update_assets=False)
# add uninstalled games
for game in sorted(self.game_list, key=lambda x: x.app_title):
if self.core.is_installed(game.app_name) or game.app_name in self.no_asset_names:
continue
i_widget, list_widget = self.widgets.get(game.app_name, (None, None))
if i_widget and list_widget:
icon_layout.addWidget(i_widget)
list_layout.addWidget(list_widget)
else:
logger.warning("Found installed game, without widget. Generating widget... ")
try:
i_widget, l_widget = self.add_uninstalled_widget(game)
if not i_widget or not l_widget:
logger.warning(f"Ignoring {game.app_name}")
continue
icon_layout.addWidget(i_widget)
list_layout.addWidget(l_widget)
except Exception as e:
logger.error(str(e))
continue
QObjectCleanupHandler().add(self.icon_view.layout())
QObjectCleanupHandler().add(self.list_view.layout())
self.icon_view.setLayout(icon_layout)
self.list_view.setLayout(list_layout)
self.icon_view.setParent(None)
self.list_view.setParent(None)
# insert widget in layout
self.scroll_widget.layout().insertWidget(
1, self.icon_view if self.head_bar.view.isChecked() else self.list_view
)
def toggle_view(self):
self.settings.setValue("icon_view", not self.head_bar.view.isChecked())
if not self.head_bar.view.isChecked():
self.scroll_widget.layout().replaceWidget(self.list_view, self.icon_view)
self.list_view.setParent(None)
self.view_stack.slideInWidget(self.icon_view_scroll)
else:
self.scroll_widget.layout().replaceWidget(self.icon_view, self.list_view)
self.icon_view.setParent(None)
self.game_list_scroll_area.verticalScrollBar().setValue(0)
self.view_stack.slideInWidget(self.list_view_scroll)

View file

@ -217,3 +217,33 @@ class BaseInstalledWidget(QFrame):
if error:
QMessageBox.warning(self, "Error", error)
self.game_running = False
# From RareGame, added from sorting to work
@property
def is_non_asset(self) -> bool:
"""!
@brief Property to report if a Game doesn't have assets
Typically, games have assets, however some games that require
other launchers do not have them. Rare treats these games as installed
offering to execute their launcher.
@return bool If the game doesn't have assets
"""
return not self.game.asset_infos
@property
def is_installed(self) -> bool:
"""!
@brief Property to report if a game is installed
This returns True if InstalledGame data have been loaded for the game
or if the game is a game without assets, for example an Origin game.
@return bool If the game should be considered installed
"""
return (self.igame is not None) or self.is_non_asset
@property
def app_title(self) -> str:
return self.igame.title if self.igame is not None else self.game.app_title

View file

@ -39,3 +39,33 @@ class BaseUninstalledWidget(QFrame):
def install(self):
self.show_uninstalled_info.emit(self.game)
# From RareGame, added from sorting to work
@property
def is_non_asset(self) -> bool:
"""!
@brief Property to report if a Game doesn't have assets
Typically, games have assets, however some games that require
other launchers do not have them. Rare treats these games as installed
offering to execute their launcher.
@return bool If the game doesn't have assets
"""
return not self.game.asset_infos
@property
def is_installed(self) -> bool:
"""!
@brief Property to report if a game is installed
This returns True if InstalledGame data have been loaded for the game
or if the game is a game without assets, for example an Origin game.
@return bool If the game should be considered installed
"""
return False or self.is_non_asset
@property
def app_title(self) -> str:
return self.game.app_title

View file

@ -20,7 +20,7 @@ class InstalledIconWidget(BaseInstalledWidget):
def __init__(self, app_name, pixmap, game_utils):
super(InstalledIconWidget, self).__init__(app_name, pixmap, game_utils)
self.setObjectName("game_widget_icon")
self.setObjectName(type(self).__name__)
self.setContextMenuPolicy(Qt.ActionsContextMenu)
layout = QVBoxLayout()

View file

@ -17,7 +17,7 @@ class InstallingGameWidget(QFrame):
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setFixedWidth(ImageSize.Display.size.width())
self.setObjectName("game_widget_icon")
self.setObjectName(type(self).__name__)
self.core = LegendaryCoreSingleton()
self.image_manager = ImageManagerSingleton()

View file

@ -14,12 +14,12 @@ from rare.widgets.elide_label import ElideLabel
logger = getLogger("Uninstalled")
class IconWidgetUninstalled(BaseUninstalledWidget):
class UninstalledIconWidget(BaseUninstalledWidget):
def __init__(self, game: Game, core: LegendaryCore, pixmap):
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
super(UninstalledIconWidget, self).__init__(game, core, pixmap)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setObjectName("game_widget_icon")
self.setObjectName(type(self).__name__)
layout.addWidget(self.image)
miniwidget = QWidget(self)

View file

@ -11,9 +11,9 @@ from rare.components.tabs.games.game_widgets.base_uninstalled_widget import (
logger = getLogger("Game")
class ListWidgetUninstalled(BaseUninstalledWidget):
class UninstalledListWidget(BaseUninstalledWidget):
def __init__(self, core: LegendaryCore, game, pixmap):
super(ListWidgetUninstalled, self).__init__(game, core, pixmap)
super(UninstalledListWidget, self).__init__(game, core, pixmap)
self.setFrameStyle(self.StyledPanel)
layout = QHBoxLayout()
self.setLayout(layout)

View file

@ -12,7 +12,8 @@ from PyQt5.QtWidgets import (
QStackedWidget,
)
from rare.utils.extra_widgets import ImageLabel, FlowLayout, WaitingSpinner
from rare.utils.extra_widgets import ImageLabel, WaitingSpinner
from rare.widgets.flow_layout import FlowLayout
class SearchResults(QStackedWidget):

View file

@ -13,7 +13,8 @@ from PyQt5.QtWidgets import (
from legendary.core import LegendaryCore
from rare.ui.components.tabs.store.store import Ui_ShopWidget
from rare.utils.extra_widgets import WaitingSpinner, FlowLayout, ButtonLineEdit
from rare.utils.extra_widgets import WaitingSpinner, ButtonLineEdit
from rare.widgets.flow_layout import FlowLayout
from .constants import Constants
from .game_widgets import GameWidget
from .shop_api_core import ShopApiCore

View file

@ -2,43 +2,44 @@
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/games_tab.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.
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_GamesTab(object):
def setupUi(self, GamesTab):
GamesTab.setObjectName("GamesTab")
GamesTab.resize(1071, 678)
GamesTab.setWindowTitle("StackedWidget")
self.games = QtWidgets.QWidget()
self.games.setObjectName("games")
self.verticalLayout = QtWidgets.QVBoxLayout(self.games)
self.verticalLayout.setObjectName("verticalLayout")
self.game_list_scroll_area = QtWidgets.QScrollArea(self.games)
self.game_list_scroll_area.setWidgetResizable(True)
self.game_list_scroll_area.setObjectName("game_list_scroll_area")
self.scroll_widget = QtWidgets.QWidget()
self.scroll_widget.setGeometry(QtCore.QRect(0, 0, 1051, 658))
self.scroll_widget.setObjectName("scroll_widget")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.scroll_widget)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.count_games_label = QtWidgets.QLabel(self.scroll_widget)
self.count_games_label.setText("")
self.library_frame = QtWidgets.QFrame(self.games)
self.library_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.library_frame.setFrameShadow(QtWidgets.QFrame.Plain)
self.library_frame.setObjectName("library_frame")
self.library_frame_layout = QtWidgets.QVBoxLayout(self.library_frame)
self.library_frame_layout.setContentsMargins(0, 0, 0, 0)
self.library_frame_layout.setObjectName("library_frame_layout")
self.games_count_layout = QtWidgets.QHBoxLayout()
self.games_count_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.games_count_layout.setContentsMargins(6, 6, 6, 6)
self.games_count_layout.setObjectName("games_count_layout")
self.count_games_label = QtWidgets.QLabel(self.library_frame)
self.count_games_label.setText("error")
self.count_games_label.setObjectName("count_games_label")
self.verticalLayout_4.addWidget(self.count_games_label)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_4.addItem(spacerItem)
self.game_list_scroll_area.setWidget(self.scroll_widget)
self.verticalLayout.addWidget(self.game_list_scroll_area)
self.games_count_layout.addWidget(self.count_games_label, 0, QtCore.Qt.AlignTop)
self.library_frame_layout.addLayout(self.games_count_layout)
self.verticalLayout.addWidget(self.library_frame)
GamesTab.addWidget(self.games)
self.retranslateUi(GamesTab)
GamesTab.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(GamesTab)
def retranslateUi(self, GamesTab):

View file

@ -2,56 +2,62 @@
<ui version="4.0">
<class>GamesTab</class>
<widget class="QStackedWidget" name="GamesTab">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1071</width>
<height>678</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">StackedWidget</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="games">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="game_list_scroll_area">
<property name="widgetResizable">
<bool>true</bool>
<widget class="QFrame" name="library_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<widget class="QWidget" name="scroll_widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1051</width>
<height>658</height>
</rect>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QVBoxLayout" name="library_frame_layout">
<property name="leftMargin">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="count_games_label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="games_count_layout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="count_games_label">
<property name="text">
<string notr="true">error</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>

View file

@ -13,7 +13,6 @@ from PyQt5.QtCore import (
)
from PyQt5.QtGui import QMovie, QPixmap, QFontMetrics, QImage
from PyQt5.QtWidgets import (
QLayout,
QStyle,
QSizePolicy,
QLabel,
@ -42,110 +41,6 @@ from rare.utils.utils import icon as qta_icon
logger = getLogger("ExtraWidgets")
class FlowLayout(QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
super(FlowLayout, self).__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
self._items = []
self.setContentsMargins(margin, margin, margin, margin)
self.setObjectName(type(self).__name__)
def __del__(self):
del self._items[:]
def addItem(self, item):
self._items.append(item)
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
def itemAt(self, index):
if 0 <= index < len(self._items):
return self._items[index]
def takeAt(self, index):
if 0 <= index < len(self._items):
return self._items.pop(index)
def expandingDirections(self):
return Qt.Orientations(0)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.doLayout(QRect(0, 0, width, 0), True)
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._items:
size = size.expandedTo(item.minimumSize())
left, top, right, bottom = self.getContentsMargins()
size += QSize(left + right, top + bottom)
return size
def doLayout(self, rect, testonly):
left, top, right, bottom = self.getContentsMargins()
effective = rect.adjusted(+left, +top, -right, -bottom)
x = effective.x()
y = effective.y()
lineheight = 0
for item in self._items:
widget = item.widget()
if not widget.isVisible():
continue
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
)
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
)
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x()
y = y + lineheight + vspace
nextX = x + item.sizeHint().width() + hspace
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() + bottom
def smartSpacing(self, pm):
parent = self.parent()
if parent is None:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()
class IndicatorReasons:
dir_not_empty = QCoreApplication.translate("IndicatorReasons", "Directory is not empty")
wrong_format = QCoreApplication.translate("IndicatorReasons", "Given text has wrong format")

115
rare/widgets/flow_layout.py Normal file
View file

@ -0,0 +1,115 @@
from PyQt5.QtCore import (
Qt,
QRect,
QSize,
QPoint,
)
from PyQt5.QtWidgets import (
QLayout,
QStyle,
QSizePolicy,
)
class FlowLayout(QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
super(FlowLayout, self).__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
self._items = []
self.setContentsMargins(margin, margin, margin, margin)
self.setObjectName(type(self).__name__)
def __del__(self):
del self._items[:]
def addItem(self, item):
self._items.append(item)
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
def itemAt(self, index):
if 0 <= index < len(self._items):
return self._items[index]
def takeAt(self, index):
if 0 <= index < len(self._items):
return self._items.pop(index)
def expandingDirections(self):
return Qt.Orientations(0)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.doLayout(QRect(0, 0, width, 0), True)
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._items:
size = size.expandedTo(item.minimumSize())
left, top, right, bottom = self.getContentsMargins()
size += QSize(left + right, top + bottom)
return size
def doLayout(self, rect, testonly):
left, top, right, bottom = self.getContentsMargins()
effective = rect.adjusted(+left, +top, -right, -bottom)
x = effective.x()
y = effective.y()
lineheight = 0
for item in self._items:
widget = item.widget()
if not widget.isVisible():
continue
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
)
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
)
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x()
y = y + lineheight + vspace
nextX = x + item.sizeHint().width() + hspace
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() + bottom
def smartSpacing(self, pm):
parent = self.parent()
if parent is None:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()

View file

@ -7,9 +7,10 @@ from PyQt5.QtCore import (
)
from PyQt5.QtWidgets import (
QSizePolicy,
QWidget,
)
from rare.utils.extra_widgets import FlowLayout
from .flow_layout import FlowLayout
class LibraryLayout(FlowLayout):
@ -133,3 +134,14 @@ class LibraryLayout(FlowLayout):
def sort(self, key: Callable):
self._items.sort(key=key)
self.setGeometry(self.parent().rect())
# These are used to pop and insert the installing widget, remove them when no longer needed
def remove(self, name: str) -> QWidget:
widget = next(filter(lambda x: x.widget().objectName() == name, self._items), None)
self._items.remove(widget)
self.setGeometry(self.parent().rect())
return widget
def insert(self, index: int, widget: QWidget):
self._items.insert(index, widget)
self.setGeometry(self.parent().rect())

View file

@ -0,0 +1,138 @@
from PyQt5.QtCore import (
pyqtSlot,
QEvent,
QEasingCurve,
QParallelAnimationGroup,
QAbstractAnimation,
QPropertyAnimation,
Qt,
QPoint,
)
from PyQt5.QtWidgets import QStackedWidget, QGestureEvent, QSwipeGesture
class SlidingStackedWidget(QStackedWidget):
"""
Taken from: https://stackoverflow.com/a/52597972
"""
def __init__(self, parent=None):
super(SlidingStackedWidget, self).__init__(parent)
self.m_direction = Qt.Horizontal
self.m_speed = 500
self.m_animationtype = QEasingCurve.OutBack
self.m_now = 0
self.m_next = 0
self.m_wrap = False
self.m_pnow = QPoint(0, 0)
self.m_active = False
def setDirection(self, direction: Qt.Orientation) -> None:
self.m_direction = direction
def setSpeed(self, speed: int) -> None:
self.m_speed = speed
def setAnimation(self, animationtype: QEasingCurve.Type) -> None:
self.m_animationtype = animationtype
def setWrap(self, wrap: bool) -> None:
self.m_wrap = wrap
@pyqtSlot()
def slideInPrev(self):
now = self.currentIndex()
if self.m_wrap or now > 0:
self.slideInIndex(now - 1)
@pyqtSlot()
def slideInNext(self):
now = self.currentIndex()
if self.m_wrap or now < (self.count() - 1):
self.slideInIndex(now + 1)
def slideInIndex(self, idx):
if idx > (self.count() - 1):
idx = idx % self.count()
elif idx < 0:
idx = (idx + self.count()) % self.count()
self.slideInWidget(self.widget(idx))
def slideInWidget(self, newwidget):
if self.m_active:
return
self.m_active = True
_now = self.currentIndex()
_next = self.indexOf(newwidget)
if _now == _next:
self.m_active = False
return
offsetx, offsety = self.frameRect().width(), self.frameRect().height()
self.widget(_next).setGeometry(self.frameRect())
if not self.m_direction == Qt.Horizontal:
if _now < _next:
offsetx, offsety = 0, -offsety
else:
offsetx = 0
else:
if _now < _next:
offsetx, offsety = -offsetx, 0
else:
offsety = 0
pnext = self.widget(_next).pos()
pnow = self.widget(_now).pos()
self.m_pnow = pnow
offset = QPoint(offsetx, offsety)
self.widget(_next).move(pnext - offset)
self.widget(_next).show()
self.widget(_next).raise_()
animgroup = QParallelAnimationGroup(self, finished=self.animationDoneSlot)
for index, start, end in zip((_now, _next), (pnow, pnext - offset), (pnow + offset, pnext)):
animation = QPropertyAnimation(
self.widget(index),
b"pos",
duration=self.m_speed,
easingCurve=self.m_animationtype,
startValue=start,
endValue=end,
)
animgroup.addAnimation(animation)
self.m_next = _next
self.m_now = _now
self.m_active = True
animgroup.start(QAbstractAnimation.DeleteWhenStopped)
@pyqtSlot()
def animationDoneSlot(self):
self.setCurrentIndex(self.m_next)
self.widget(self.m_now).hide()
self.widget(self.m_now).move(self.m_pnow)
self.m_active = False
def event(self, e: QEvent):
if e.type() == QEvent.Gesture:
return self.gestureEvent(QGestureEvent(e))
return super(SlidingStackedWidget, self).event(e)
def gestureEvent(self, e: QGestureEvent):
if swipe := e.gesture(Qt.SwipeGesture):
self.swipeTriggered(swipe)
return True
def swipeTriggered(self, g: QSwipeGesture):
if g.state() == Qt.GestureFinished:
if g.horizontalDirection() == QSwipeGesture.Left:
self.slideInPrev()
else:
self.slideInNext()