LibraryLayout from #196
Introduces the LibraryLayout from #196. This layout distributes the available space in either horizontal side and in-between the widgets. Known issues: When searching for a game, it will re-align visible widgets, effectively centering the results. This is because the search and grouping functions are interleaved. #196 handles it differently by adjusting the opacity and re-ordering of the irrelevant widgets. Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
This commit is contained in:
parent
d772c4000e
commit
08ab130c5c
11 changed files with 159 additions and 18 deletions
|
@ -13,8 +13,7 @@ from rare.shared import (
|
||||||
)
|
)
|
||||||
from rare.shared.image_manager import ImageManagerSingleton
|
from rare.shared.image_manager import ImageManagerSingleton
|
||||||
from rare.ui.components.tabs.games.games_tab import Ui_GamesTab
|
from rare.ui.components.tabs.games.games_tab import Ui_GamesTab
|
||||||
from rare.utils.extra_widgets import FlowLayout
|
from rare.widgets.library_layout import LibraryLayout
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
from .cloud_save_utils import CloudSaveUtils
|
||||||
from .game_info import GameInfoTabs
|
from .game_info import GameInfoTabs
|
||||||
from .game_info.uninstalled_info import UninstalledInfoTabs
|
from .game_info.uninstalled_info import UninstalledInfoTabs
|
||||||
|
@ -181,7 +180,7 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
||||||
|
|
||||||
def setup_game_list(self):
|
def setup_game_list(self):
|
||||||
self.icon_view = QWidget()
|
self.icon_view = QWidget()
|
||||||
self.icon_view.setLayout(FlowLayout())
|
self.icon_view.setLayout(LibraryLayout())
|
||||||
self.list_view = QWidget()
|
self.list_view = QWidget()
|
||||||
self.list_view.setLayout(QVBoxLayout())
|
self.list_view.setLayout(QVBoxLayout())
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ class GameProcess(QObject):
|
||||||
self.app_name = app_name
|
self.app_name = app_name
|
||||||
self.on_startup = on_startup
|
self.on_startup = on_startup
|
||||||
self.game = LegendaryCoreSingleton().get_game(app_name)
|
self.game = LegendaryCoreSingleton().get_game(app_name)
|
||||||
|
self.game_meta = RareGameMeta()
|
||||||
self.socket = QLocalSocket()
|
self.socket = QLocalSocket()
|
||||||
self.socket.connected.connect(self._socket_connected)
|
self.socket.connected.connect(self._socket_connected)
|
||||||
self.socket.errorOccurred.connect(self._error_occurred)
|
self.socket.errorOccurred.connect(self._error_occurred)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray
|
from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray
|
||||||
from PyQt5.QtGui import QPixmap
|
from PyQt5.QtGui import QPixmap
|
||||||
from PyQt5.QtWidgets import QGroupBox, QMessageBox, QAction
|
from PyQt5.QtWidgets import QFrame, QMessageBox, QAction
|
||||||
|
|
||||||
from rare.components.tabs.games.game_utils import GameUtils
|
from rare.components.tabs.games.game_utils import GameUtils
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
|
@ -15,7 +15,7 @@ from rare.widgets.image_widget import ImageWidget
|
||||||
logger = getLogger("Game")
|
logger = getLogger("Game")
|
||||||
|
|
||||||
|
|
||||||
class BaseInstalledWidget(QGroupBox):
|
class BaseInstalledWidget(QFrame):
|
||||||
launch_signal = pyqtSignal(str, QProcess, list)
|
launch_signal = pyqtSignal(str, QProcess, list)
|
||||||
show_info = pyqtSignal(str)
|
show_info = pyqtSignal(str)
|
||||||
finish_signal = pyqtSignal(str, int)
|
finish_signal = pyqtSignal(str, int)
|
||||||
|
@ -83,7 +83,6 @@ class BaseInstalledWidget(QGroupBox):
|
||||||
self.update_available = True
|
self.update_available = True
|
||||||
|
|
||||||
self.data = QByteArray()
|
self.data = QByteArray()
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
|
|
||||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt
|
from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
from PyQt5.QtWidgets import QGroupBox, QAction
|
from PyQt5.QtWidgets import QFrame, QAction
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
|
|
||||||
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||||
|
@ -10,7 +10,7 @@ from rare.widgets.image_widget import ImageWidget
|
||||||
logger = getLogger("Uninstalled")
|
logger = getLogger("Uninstalled")
|
||||||
|
|
||||||
|
|
||||||
class BaseUninstalledWidget(QGroupBox):
|
class BaseUninstalledWidget(QFrame):
|
||||||
show_uninstalled_info = pyqtSignal(Game)
|
show_uninstalled_info = pyqtSignal(Game)
|
||||||
|
|
||||||
def __init__(self, game, core, pixmap):
|
def __init__(self, game, core, pixmap):
|
||||||
|
@ -27,7 +27,6 @@ class BaseUninstalledWidget(QGroupBox):
|
||||||
self.image.setPixmap(pixmap)
|
self.image.setPixmap(pixmap)
|
||||||
self.installing = False
|
self.installing = False
|
||||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
|
||||||
reload_image = QAction(self.tr("Reload Image"), self)
|
reload_image = QAction(self.tr("Reload Image"), self)
|
||||||
reload_image.triggered.connect(self.reload_image)
|
reload_image.triggered.connect(self.reload_image)
|
||||||
|
|
|
@ -24,6 +24,8 @@ class InstalledIconWidget(BaseInstalledWidget):
|
||||||
|
|
||||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.setFixedWidth(ImageSize.Display.size.width())
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
|
|
||||||
if self.update_available:
|
if self.update_available:
|
||||||
|
@ -42,6 +44,7 @@ class InstalledIconWidget(BaseInstalledWidget):
|
||||||
miniwidget.setLayout(minilayout)
|
miniwidget.setLayout(minilayout)
|
||||||
|
|
||||||
self.title_label = ElideLabel(f"<h4>{self.game.app_title}</h4>", parent=miniwidget)
|
self.title_label = ElideLabel(f"<h4>{self.game.app_title}</h4>", parent=miniwidget)
|
||||||
|
self.title_label.setAlignment(Qt.AlignTop)
|
||||||
self.title_label.setObjectName("game_widget")
|
self.title_label.setObjectName("game_widget")
|
||||||
minilayout.addWidget(self.title_label, stretch=2)
|
minilayout.addWidget(self.title_label, stretch=2)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class InstalledListWidget(BaseInstalledWidget):
|
||||||
|
|
||||||
def __init__(self, app_name, pixmap, game_utils):
|
def __init__(self, app_name, pixmap, game_utils):
|
||||||
super(InstalledListWidget, self).__init__(app_name, pixmap, game_utils)
|
super(InstalledListWidget, self).__init__(app_name, pixmap, game_utils)
|
||||||
|
self.setFrameStyle(self.StyledPanel)
|
||||||
self.dev = self.game.metadata["developer"]
|
self.dev = self.game.metadata["developer"]
|
||||||
if self.igame:
|
if self.igame:
|
||||||
self.size = self.igame.install_size
|
self.size = self.igame.install_size
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QPixmap
|
from PyQt5.QtGui import QPixmap
|
||||||
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGroupBox, QWidget
|
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QFrame
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
|
|
||||||
from rare.shared import LegendaryCoreSingleton
|
from rare.shared import LegendaryCoreSingleton
|
||||||
|
@ -9,14 +9,15 @@ from rare.widgets.elide_label import ElideLabel
|
||||||
from .library_widget import LibraryWidget
|
from .library_widget import LibraryWidget
|
||||||
|
|
||||||
|
|
||||||
class InstallingGameWidget(QGroupBox):
|
class InstallingGameWidget(QFrame):
|
||||||
game: Game = None
|
game: Game = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(InstallingGameWidget, self).__init__()
|
super(InstallingGameWidget, self).__init__()
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.setFixedWidth(ImageSize.Display.size.width())
|
||||||
self.setObjectName("game_widget_icon")
|
self.setObjectName("game_widget_icon")
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.image_manager = ImageManagerSingleton()
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
@ -34,6 +35,7 @@ class InstallingGameWidget(QGroupBox):
|
||||||
miniwidget.setLayout(minilayout)
|
miniwidget.setLayout(minilayout)
|
||||||
|
|
||||||
self.title_label = ElideLabel(f"<h4>Error</h4>", parent=miniwidget)
|
self.title_label = ElideLabel(f"<h4>Error</h4>", parent=miniwidget)
|
||||||
|
self.title_label.setAlignment(Qt.AlignTop)
|
||||||
self.title_label.setObjectName("game_widget")
|
self.title_label.setObjectName("game_widget")
|
||||||
minilayout.addWidget(self.title_label, stretch=2)
|
minilayout.addWidget(self.title_label, stretch=2)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
|
||||||
def __init__(self, game: Game, core: LegendaryCore, pixmap):
|
def __init__(self, game: Game, core: LegendaryCore, pixmap):
|
||||||
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
|
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.setObjectName("game_widget_icon")
|
self.setObjectName("game_widget_icon")
|
||||||
layout.addWidget(self.image)
|
layout.addWidget(self.image)
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
|
||||||
miniwidget.setLayout(minilayout)
|
miniwidget.setLayout(minilayout)
|
||||||
|
|
||||||
self.title_label = ElideLabel(f"<h4>{game.app_title}</h4>", parent=miniwidget)
|
self.title_label = ElideLabel(f"<h4>{game.app_title}</h4>", parent=miniwidget)
|
||||||
|
self.title_label.setAlignment(Qt.AlignTop)
|
||||||
self.title_label.setObjectName("game_widget")
|
self.title_label.setObjectName("game_widget")
|
||||||
minilayout.addWidget(self.title_label, stretch=2)
|
minilayout.addWidget(self.title_label, stretch=2)
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,13 @@ logger = getLogger("Game")
|
||||||
class ListWidgetUninstalled(BaseUninstalledWidget):
|
class ListWidgetUninstalled(BaseUninstalledWidget):
|
||||||
def __init__(self, core: LegendaryCore, game, pixmap):
|
def __init__(self, core: LegendaryCore, game, pixmap):
|
||||||
super(ListWidgetUninstalled, self).__init__(game, core, pixmap)
|
super(ListWidgetUninstalled, self).__init__(game, core, pixmap)
|
||||||
self.layout = QHBoxLayout()
|
self.setFrameStyle(self.StyledPanel)
|
||||||
self.setLayout(self.layout)
|
layout = QHBoxLayout()
|
||||||
self.layout.addWidget(self.image)
|
self.setLayout(layout)
|
||||||
|
layout.addWidget(self.image)
|
||||||
|
|
||||||
self.child_layout = QVBoxLayout()
|
self.child_layout = QVBoxLayout()
|
||||||
self.layout.addLayout(self.child_layout)
|
layout.addLayout(self.child_layout)
|
||||||
|
|
||||||
self.title_label = QLabel(f"<h2>{self.game.app_title}</h2>")
|
self.title_label = QLabel(f"<h2>{self.game.app_title}</h2>")
|
||||||
self.app_name_label = QLabel(f"App Name: {self.game.app_name}")
|
self.app_name_label = QLabel(f"App Name: {self.game.app_name}")
|
||||||
|
@ -31,5 +32,5 @@ class ListWidgetUninstalled(BaseUninstalledWidget):
|
||||||
self.child_layout.addWidget(self.app_name_label)
|
self.child_layout.addWidget(self.app_name_label)
|
||||||
self.child_layout.addWidget(self.install_button)
|
self.child_layout.addWidget(self.install_button)
|
||||||
|
|
||||||
self.layout.setAlignment(Qt.AlignLeft)
|
layout.setAlignment(Qt.AlignLeft)
|
||||||
self.child_layout.setAlignment(Qt.AlignTop)
|
self.child_layout.setAlignment(Qt.AlignTop)
|
||||||
|
|
|
@ -4,7 +4,6 @@ from typing import Callable, Tuple
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt,
|
Qt,
|
||||||
QEvent,
|
|
||||||
QCoreApplication,
|
QCoreApplication,
|
||||||
QRect,
|
QRect,
|
||||||
QSize,
|
QSize,
|
||||||
|
|
135
rare/widgets/library_layout.py
Normal file
135
rare/widgets/library_layout.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from PyQt5.QtCore import (
|
||||||
|
Qt,
|
||||||
|
QRect,
|
||||||
|
QPoint,
|
||||||
|
)
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QSizePolicy,
|
||||||
|
)
|
||||||
|
|
||||||
|
from rare.utils.extra_widgets import FlowLayout
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryLayout(FlowLayout):
|
||||||
|
def __init__(self, parent=None, margin=6, spacing=11):
|
||||||
|
super(LibraryLayout, self).__init__(parent=parent, margin=margin, hspacing=spacing, vspacing=spacing)
|
||||||
|
|
||||||
|
# def event(self, e: QEvent) -> None:
|
||||||
|
# if e.type() == QEvent.ShowToParent or e.type() == QEvent.HideToParent:
|
||||||
|
# self.doLayout(self.parent().rect(), False)
|
||||||
|
# e.accept()
|
||||||
|
|
||||||
|
def expandingDirections(self):
|
||||||
|
return Qt.Orientations(Qt.Horizontal | Qt.Vertical)
|
||||||
|
|
||||||
|
def setGeometry(self, rect):
|
||||||
|
super(FlowLayout, self).setGeometry(rect)
|
||||||
|
self.doLayout(rect, False)
|
||||||
|
|
||||||
|
def doLayout(self, rect, testonly):
|
||||||
|
"""!
|
||||||
|
@brief Arranges the widgets for this layout
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
Layout
|
||||||
|
+-----------------------------------------------+
|
||||||
|
| margin (above first row only) |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| vspace (hspace) |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| hpadding |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| _ _ __ +--------+ _ __ +--------+ _ __ _ |
|
||||||
|
||m||h|| h|| Widget ||h|| h|| Widget ||h|| h||m||
|
||||||
|
||a||s|| p|| ||s|| p|| ||s|| p||a||
|
||||||
|
||r||p|| a|| ||p|| a|| ||p|| a||r||
|
||||||
|
||g||a|| d|| ||a|| d|| ||a|| d||g||
|
||||||
|
||i||c|| d|| ||c|| d|| ||c|| d||i||
|
||||||
|
||n||e|| i|| ||e|| i|| ||e|| i||n||
|
||||||
|
|| || || n|| || || n|| || || n|| ||
|
||||||
|
|| || || g|| || || g|| || || g|| ||
|
||||||
|
| - - -- +--------+ - -- +--------+ - -- - |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| vspace (hspace) |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| hpadding |
|
||||||
|
|-----------------------------------------------|
|
||||||
|
| margin (below last row only) |
|
||||||
|
+-----------------------------------------------+
|
||||||
|
|
||||||
|
margin: doesn't play a role in the code below, it only affects the
|
||||||
|
effective rectangle inside which we can layout
|
||||||
|
hspace: static padding between widgets (minimum distance between them)
|
||||||
|
hpadding: dynamic padding to fill the space when resizing
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
@param self: object self-reference
|
||||||
|
@param rect: the area the widgets should occupy
|
||||||
|
@param testonly: only test the layout, don't arrange the items
|
||||||
|
|
||||||
|
@return: the height of the layout
|
||||||
|
"""
|
||||||
|
|
||||||
|
left, top, right, bottom = self.getContentsMargins()
|
||||||
|
effective = rect.adjusted(+left, +top, -right, -bottom)
|
||||||
|
x = effective.x()
|
||||||
|
y = effective.y()
|
||||||
|
lineheight = 0
|
||||||
|
|
||||||
|
if not self._items:
|
||||||
|
return y + lineheight - rect.y() + bottom
|
||||||
|
|
||||||
|
widget = self._items[0].widget()
|
||||||
|
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)
|
||||||
|
|
||||||
|
# lk: get the remaining space after subtracting the space required for each widget and its static padding
|
||||||
|
# lk: also reserve space for the leading static padding
|
||||||
|
rem_hspace = (effective.width() - hspace) % (widget.size().width() + hspace)
|
||||||
|
# lk: the number of items and their static spacing that can be in the layout
|
||||||
|
hspace_items = (effective.width() - hspace) // (widget.size().width() + hspace)
|
||||||
|
|
||||||
|
# lk: in case the visible items are less than the maximum possible widgets
|
||||||
|
visible_items = len([item for item in self._items if not item.isEmpty()])
|
||||||
|
if visible_items < hspace_items:
|
||||||
|
hspace_items = visible_items
|
||||||
|
rem_hspace = (effective.width() - hspace) - ((widget.size().width() + hspace) * hspace_items)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# lk: the dynamic padding between each item, also account for the leading dynamic padding
|
||||||
|
hpadding = rem_hspace // (hspace_items + 1)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
hpadding = 0
|
||||||
|
|
||||||
|
for item in self._items:
|
||||||
|
if item.isEmpty():
|
||||||
|
continue
|
||||||
|
# lk: compute the location of the next widget
|
||||||
|
next_x = x + item.sizeHint().width() + hspace + hpadding
|
||||||
|
# lk: find out if there is enough space for the widget in this row
|
||||||
|
# lk: account for the leading static and dynamic padding too (at the start)
|
||||||
|
if next_x - hspace * 2 - hpadding * 2 > effective.right() and lineheight > 0:
|
||||||
|
x = effective.x()
|
||||||
|
# lk: find next vertical position, add static and dynamic padding
|
||||||
|
y = y + lineheight + vspace + hpadding
|
||||||
|
next_x = x + item.sizeHint().width() + hspace + hpadding
|
||||||
|
lineheight = 0
|
||||||
|
# lk: add static and dynamic padding to the current widget
|
||||||
|
x = x + hspace + hpadding
|
||||||
|
if not testonly:
|
||||||
|
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||||
|
x = next_x
|
||||||
|
lineheight = max(lineheight, item.sizeHint().height())
|
||||||
|
return y + lineheight - rect.y() + bottom
|
||||||
|
|
||||||
|
def sort(self, key: Callable):
|
||||||
|
self._items.sort(key=key)
|
||||||
|
self.setGeometry(self.parent().rect())
|
Loading…
Reference in a new issue