feat: improve library filter controls #195

Merged
Boria138 merged 3 commits from library-filter into main 2026-06-08 16:43:22 +00:00
10 changed files with 723 additions and 79 deletions

No files matched your search

+3
View File
@@ -1,9 +1,12 @@
from portprotonqt.animations.detail_page import DetailPageAnimations
from portprotonqt.animations.game_card import GameCardAnimations
from portprotonqt.animations.library_controls import ExpandingSearchAnimation, LibraryControlsAnimation
from portprotonqt.animations.virtual_keyboard import VirtualKeyboardAnimations
__all__ = [
"DetailPageAnimations",
"ExpandingSearchAnimation",
"GameCardAnimations",
"LibraryControlsAnimation",
"VirtualKeyboardAnimations",
]
+117
View File
@@ -0,0 +1,117 @@
from PySide6.QtCore import (
QAbstractAnimation,
QByteArray,
QEasingCurve,
QParallelAnimationGroup,
QPropertyAnimation,
)
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
class ExpandingSearchAnimation:
def __init__(self, widget: QWidget, theme: object, fallback_duration: int):
self.widget = widget
self.theme = theme
self.fallback_duration = fallback_duration
self.animation = QPropertyAnimation(widget, QByteArray(b"maximumWidth"))
def setup(self, collapsed_width: int, expanded_width: int) -> None:
self.collapsed_width = collapsed_width
self.expanded_width = expanded_width
self.widget.setMaximumWidth(collapsed_width)
def expand(self) -> None:
self._start(self.expanded_width)
def collapse(self) -> None:
if getattr(self.widget, "text", lambda: "")():
return
self._start(self.collapsed_width)
def _start(self, target_width: int) -> None:
if self.animation.state() == QAbstractAnimation.State.Running:
self.animation.stop()
self.animation = QPropertyAnimation(self.widget, QByteArray(b"maximumWidth"))
self.animation.setDuration(self._duration())
self.animation.setStartValue(self.widget.maximumWidth())
self.animation.setEndValue(target_width)
self.animation.setEasingCurve(self._easing(target_width == self.expanded_width))
self.animation.start()
def _duration(self) -> int:
animation_config = getattr(self.theme, "GAME_CARD_ANIMATION", {})
return animation_config.get("scale_anim_duration", self.fallback_duration)
def _easing(self, opening: bool) -> QEasingCurve:
animation_config = getattr(self.theme, "GAME_CARD_ANIMATION", {})
key = "scale_easing_curve" if opening else "scale_easing_curve_out"
easing_name = animation_config.get(key, QEasingCurve.Type.InOutQuad.name)
easing_type = getattr(QEasingCurve.Type, easing_name, QEasingCurve.Type.InOutQuad)
return QEasingCurve(easing_type)
class LibraryControlsAnimation:
def __init__(self, widget: QWidget, theme: object, fallback_duration: int):
self.widget = widget
self.theme = theme
self.fallback_duration = fallback_duration
self.opacity_effect = QGraphicsOpacityEffect(widget)
self.opacity_effect.setOpacity(0)
self.widget.setGraphicsEffect(self.opacity_effect)
self.group = QParallelAnimationGroup(widget)
def setup_hidden(self) -> None:
self.widget.setVisible(False)
self.widget.setMaximumHeight(0)
def toggle(self, opening: bool) -> None:
if self.group.state() == QAbstractAnimation.State.Running:
self.group.stop()
if opening:
self.widget.setVisible(True)
target_height = self.widget.sizeHint().height() if opening else 0
self.group = QParallelAnimationGroup(self.widget)
height_animation = self._create_widget_animation(
b"maximumHeight",
self.widget.maximumHeight(),
target_height,
)
opacity_animation = self._create_opacity_animation(opening, height_animation.duration())
self.group.addAnimation(height_animation)
self.group.addAnimation(opacity_animation)
if not opening:
self.group.finished.connect(self.widget.hide)
self.group.start()
def _create_widget_animation(
self,
property_name: bytes,
start_value: object,
end_value: object,
) -> QPropertyAnimation:
animation = QPropertyAnimation(self.widget, QByteArray(property_name))
animation.setDuration(self._duration())
animation.setStartValue(start_value)
animation.setEndValue(end_value)
animation.setEasingCurve(self._easing(end_value != 0))
return animation
def _create_opacity_animation(self, opening: bool, duration: int) -> QPropertyAnimation:
animation = QPropertyAnimation(self.opacity_effect, QByteArray(b"opacity"))
animation.setDuration(duration)
animation.setStartValue(self.opacity_effect.opacity())
animation.setEndValue(1 if opening else 0)
animation.setEasingCurve(self._easing(opening))
return animation
def _duration(self) -> int:
animation_config = getattr(self.theme, "GAME_CARD_ANIMATION", {})
return animation_config.get("scale_anim_duration", self.fallback_duration)
def _easing(self, opening: bool) -> QEasingCurve:
animation_config = getattr(self.theme, "GAME_CARD_ANIMATION", {})
key = "scale_easing_curve" if opening else "scale_easing_curve_out"
easing_name = animation_config.get(key, QEasingCurve.Type.InOutQuad.name)
easing_type = getattr(QEasingCurve.Type, easing_name, QEasingCurve.Type.InOutQuad)
return QEasingCurve(easing_type)
+22 -3
View File
@@ -1,6 +1,7 @@
from typing import Protocol
from portprotonqt.game_card import GameCard
from portprotonqt.search_utils import SearchOptimizer, ThreadedSearch
from PySide6.QtWidgets import QAbstractButton
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QScrollArea, QSlider, QScroller, QStackedWidget
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QRegion
@@ -197,6 +198,7 @@ class GameLibraryManager:
card = self.game_card_cache[card_key]
if is_focused:
self._collapse_library_filter_controls()
if self.main_window.current_hovered_card and self.main_window.current_hovered_card != card:
try:
self.main_window.current_hovered_card._hovered = False
@@ -215,6 +217,15 @@ class GameLibraryManager:
if self.main_window.current_focused_card == card:
self.main_window.current_focused_card = None
def _collapse_library_filter_controls(self) -> None:
controls_button = getattr(self.main_window, "libraryControlsButton", None)
if not isinstance(controls_button, QAbstractButton) or not controls_button.isChecked():
return
controls_button.setChecked(False)
toggle_controls = getattr(self.main_window, "_toggle_library_controls", None)
if callable(toggle_controls):
toggle_controls()
def _on_card_hovered(self, game_name: str, is_hovered: bool):
"""Handles card hover events."""
card_key = None
@@ -229,6 +240,7 @@ class GameLibraryManager:
card = self.game_card_cache[card_key]
if is_hovered:
self._collapse_library_filter_controls()
if self.main_window.current_focused_card and self.main_window.current_focused_card != card:
try:
if self.main_window.current_focused_card:
@@ -255,8 +267,15 @@ class GameLibraryManager:
self._pending_update = False
self._update_game_grid_immediate()
def update_game_grid(self, games_list: list[tuple] | None = None, is_filter: bool = False):
def update_game_grid(
self,
games_list: list[tuple] | None = None,
is_filter: bool = False,
focus_first_card: bool | None = None,
):
"""Schedules a game grid update with debouncing."""
if focus_first_card is not None:
self._focus_first_card_after_update = focus_first_card
self.layout_mode = str(getattr(self.theme, "LIBRARY_LAYOUT_MODE", "grid")).lower()
if self.sizeSlider is not None:
self.sizeSlider.setVisible(self.layout_mode != "list")
@@ -666,11 +685,11 @@ class GameLibraryManager:
self.game_card_cache.clear()
self.pending_images.clear()
def set_games(self, games: list[tuple]):
def set_games(self, games: list[tuple], focus_first_card: bool = True):
"""Sets the games list and updates the filtered games."""
self.games = games
self.filtered_games = self.games
self._focus_first_card_after_update = bool(games)
self._focus_first_card_after_update = bool(games) and focus_first_card
# Build search indices for fast searching
self._build_search_indices(games)
+52 -1
View File
@@ -3687,12 +3687,44 @@ class InputManager(QObject):
def _get_library_toolbar_widgets(self) -> list[QWidget]:
widgets = []
for attr_name in ("quickLaunchButton", "addGameButton", "refreshButton", "searchEdit"):
for attr_name in (
"quickLaunchButton",
"addGameButton",
"refreshButton",
"searchEdit",
"libraryControlsButton",
):
widget = getattr(self._parent, attr_name, None)
if isinstance(widget, QWidget) and widget.isVisible() and widget.isEnabled():
widgets.append(widget)
return widgets
def _get_library_filter_widgets(self) -> list[QWidget]:
controls_widget = getattr(self._parent, "libraryControlsWidget", None)
if not isinstance(controls_widget, QWidget) or not controls_widget.isVisible():
return []
widgets = []
for attr_name in ("gamesSortCombo", "gamesDisplayCombo"):
widget = getattr(self._parent, attr_name, None)
if isinstance(widget, QWidget) and widget.isVisible() and widget.isEnabled():
widgets.append(widget)
return widgets
def _focus_library_filter_controls(self) -> bool:
controls_button = getattr(self._parent, "libraryControlsButton", None)
if not isinstance(controls_button, AutoSizeButton):
return False
if not controls_button.isChecked():
controls_button.setChecked(True)
toggle_controls = getattr(self._parent, "_toggle_library_controls", None)
if callable(toggle_controls):
toggle_controls()
filter_widgets = self._get_library_filter_widgets()
if not filter_widgets:
return False
filter_widgets[0].setFocus(Qt.FocusReason.OtherFocusReason)
return True
def _focus_first_library_card(self) -> bool:
container = getattr(self._parent, "gamesListWidget", None)
if container is None:
@@ -3712,7 +3744,23 @@ class InputManager(QObject):
if value == 0 or self._parent.stackedWidget.currentIndex() != 0:
return False
toolbar_widgets = self._get_library_toolbar_widgets()
filter_widgets = self._get_library_filter_widgets()
focused = self._focused_widget()
if focused in filter_widgets:
if code == PAD_DPAD_X:
current_index = filter_widgets.index(cast(QWidget, focused))
next_index = current_index + (1 if value > 0 else -1)
if 0 <= next_index < len(filter_widgets):
filter_widgets[next_index].setFocus(Qt.FocusReason.OtherFocusReason)
return True
if code != PAD_DPAD_Y:
return False
if value < 0:
controls_button = getattr(self._parent, "libraryControlsButton", None)
if isinstance(controls_button, QWidget):
controls_button.setFocus(Qt.FocusReason.OtherFocusReason)
return True
return self._focus_first_library_card()
if focused not in toolbar_widgets:
return False
if code == PAD_DPAD_X:
@@ -3724,6 +3772,9 @@ class InputManager(QObject):
if code != PAD_DPAD_Y:
return False
if value > 0:
controls_button = getattr(self._parent, "libraryControlsButton", None)
if focused is controls_button and self._focus_library_filter_controls():
return True
return self._focus_first_library_card()
tab_button = self._parent.tabButtons.get(0)
if tab_button is not None and tab_button.isVisible():
+177 -75
View File
@@ -12,7 +12,7 @@ from portprotonqt.logger import get_logger
from portprotonqt.icon_extractor import generate_thumbnail
from portprotonqt.dialogs import AddGameDialog, FileExplorer, WinetricksDialog, ExeSettingsDialog
from portprotonqt.game_card import GameCard
from portprotonqt.animations import DetailPageAnimations
from portprotonqt.animations import DetailPageAnimations, ExpandingSearchAnimation, LibraryControlsAnimation
from portprotonqt.custom_widgets import ClickableLabel, AutoSizeButton, NavLabel, FlowLayout
from portprotonqt.detail_pages import DetailPageManager
from portprotonqt.portproton_api import PortProtonAPI, get_user_conf_setting, set_user_conf_setting
@@ -75,7 +75,7 @@ from portprotonqt.tabs.workers import MainWindowWorkersMixin
from portprotonqt.settings_manager import get_available_prefix_options, get_available_wine_options
from PySide6.QtWidgets import (QLineEdit, QMainWindow, QWidget, QVBoxLayout, QLabel, QHBoxLayout, QStackedWidget, QComboBox,
QDialog, QFormLayout, QMessageBox, QApplication, QPushButton, QProgressBar, QCheckBox, QSizePolicy, QGridLayout, QScrollArea, QScroller, QSlider, QFrame)
QDialog, QFormLayout, QMessageBox, QApplication, QPushButton, QProgressBar, QCheckBox, QSizePolicy, QGridLayout, QScrollArea, QScroller, QSlider, QFrame, QToolButton)
from PySide6.QtCore import Qt, QAbstractAnimation, QEvent, QUrl, Signal, QTimer, Slot, QProcess, QProcessEnvironment, QFileSystemWatcher, QStandardPaths
from PySide6.QtGui import QIcon, QColor, QDesktopServices, QHideEvent, QShowEvent
from typing import cast
@@ -769,7 +769,9 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
def on_games_loaded(self, games: list[tuple]):
self._loading_games = False
self.games = games
self.game_library_manager.set_games(games)
focus_first_card = not getattr(self, "_preserve_library_focus_after_load", False)
self._preserve_library_focus_after_load = False
self.game_library_manager.set_games(games, focus_first_card=focus_first_card)
self._known_portproton_desktops = self._get_portproton_desktop_files()
# Clear the refresh in progress flag
@@ -1220,39 +1222,81 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
if index == 0 and self.isVisible() and not self.games:
self.loadGames(force_load=True)
def createSearchWidget(self) -> tuple[QWidget, CustomLineEdit]:
self.container = QWidget()
self.container.setStyleSheet(self.theme.CONTAINER_STYLE)
layout = QHBoxLayout(self.container)
layout.setContentsMargins(0, 6, 0, 0)
layout.setSpacing(10)
def _set_combo_current_key(self, combo: QComboBox, keys: list[str], current: str) -> None:
try:
idx = keys.index(current)
except ValueError:
idx = 0
combo.setCurrentIndex(idx)
# Quick Launch button
def _create_library_combo(self, labels: list[str], tooltip: str) -> QComboBox:
combo = QComboBox()
combo.view().window().setWindowFlags(
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
)
combo.view().window().setAttribute(
Qt.WidgetAttribute.WA_TranslucentBackground
)
combo.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
combo.addItems(labels)
combo.setToolTip(tooltip)
combo_style = getattr(self.theme, "LIBRARY_FILTER_COMBOBOX_STYLE", self.theme.COMBOBOX_STYLE)
combo.setStyleSheet(combo_style + self.theme.SCROLL_STYLE)
combo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
return combo
def _on_library_sort_changed(self, index: int) -> None:
if index < 0 or index >= len(self.sort_keys):
return
game_config.set_sort_method(self.sort_keys[index])
if hasattr(self, "game_library_manager"):
self.game_library_manager.update_game_grid(focus_first_card=False)
def _on_library_filter_changed(self, index: int) -> None:
if index < 0 or index >= len(self.filter_keys):
return
game_config.set_display_filter(self.filter_keys[index])
self.searchEdit.clear()
self.games = []
self._preserve_library_focus_after_load = True
self.loadGames(force_load=True)
def _toggle_library_controls(self) -> None:
self.libraryControlsAnimation.toggle(self.libraryControlsButton.isChecked())
def _create_library_controls_widget(self) -> QHBoxLayout:
self.libraryControlsWidget = QWidget()
controls_layout = QHBoxLayout(self.libraryControlsWidget)
controls_layout.setContentsMargins(0, 0, 0, 0)
controls_layout.setSpacing(10)
return controls_layout
def _add_library_action_buttons(self, buttons_layout: QHBoxLayout) -> None:
self.quickLaunchButton = AutoSizeButton(_("Quick Launch"), icon=self.theme_manager.get_icon("play", as_path=True))
self.quickLaunchButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE)
self.quickLaunchButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.quickLaunchButton.clicked.connect(self.quickLaunch)
layout.addWidget(self.quickLaunchButton)
buttons_layout.addWidget(self.quickLaunchButton)
self.addGameButton = AutoSizeButton(_("Add a shortcut"), icon=self.theme_manager.get_icon("addgame", as_path=True))
self.addGameButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE)
self.addGameButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.addGameButton.clicked.connect(self.openAddGameDialog)
layout.addWidget(self.addGameButton)
buttons_layout.addWidget(self.addGameButton)
# Refresh button
self.refreshButton = AutoSizeButton(_("Refresh Grid"), icon=self.theme_manager.get_icon("update", as_path=True))
self.refreshButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE)
self.refreshButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.refreshButton.clicked.connect(self.refreshGames)
layout.addWidget(self.refreshButton)
layout.addStretch() # Add stretch to push search to the right
buttons_layout.addWidget(self.refreshButton)
buttons_layout.addStretch()
def _add_library_search(self, buttons_layout: QHBoxLayout) -> None:
self.searchEdit = CustomLineEdit(self, theme=self.theme)
icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
action_pos = cast(QLineEdit.ActionPosition, QLineEdit.ActionPosition.LeadingPosition)
self.searchEdit.addAction(icon, action_pos)
self.searchIconAction = self.searchEdit.addAction(icon, action_pos)
self.searchEdit.setMaximumWidth(200)
self.searchEdit.setPlaceholderText(_("Search ..."))
self.searchEdit.setClearButtonEnabled(True)
@@ -1263,8 +1307,122 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
self.searchDebounceTimer.setSingleShot(True)
self.searchDebounceTimer.setInterval(150) # Reduced debounce time for better responsiveness
self.searchDebounceTimer.timeout.connect(self.on_search_changed)
self.searchEdit.focusInEvent = self._wrap_search_focus_event(
self.searchEdit.focusInEvent,
True,
)
self.searchEdit.focusOutEvent = self._wrap_search_focus_event(
self.searchEdit.focusOutEvent,
False,
)
self.searchEdit.resizeEvent = self._wrap_search_resize_event(self.searchEdit.resizeEvent)
buttons_layout.addWidget(self.searchEdit)
layout.addWidget(self.searchEdit)
def _add_library_controls_button(self, buttons_layout: QHBoxLayout) -> None:
self.libraryControlsButton = AutoSizeButton(
icon=self.theme_manager.get_icon("menu", as_path=True)
)
button_style = getattr(
self.theme,
"LIBRARY_CONTROLS_BUTTON_STYLE",
self.theme.ADDGAME_BACK_BUTTON_STYLE,
)
self.libraryControlsButton.setStyleSheet(button_style)
self.libraryControlsButton.setToolTip(_("Library Settings"))
self.libraryControlsButton.setCheckable(True)
self.libraryControlsButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.libraryControlsButton.clicked.connect(self._toggle_library_controls)
buttons_layout.addWidget(self.libraryControlsButton)
def _setup_library_search_animation(self) -> None:
self.searchAnimation = ExpandingSearchAnimation(
self.searchEdit,
self.theme,
self.searchDebounceTimer.interval(),
)
collapsed_width = self.libraryControlsButton.sizeHint().width()
expanded_width = self.searchEdit.maximumWidth()
self.searchAnimation.setup(collapsed_width, expanded_width)
QTimer.singleShot(0, self._center_collapsed_search_icon)
def _wrap_search_focus_event(self, original_event: Callable, expand: bool) -> Callable:
def handle_focus_event(event):
original_event(event)
if not hasattr(self, "searchAnimation"):
return
if expand:
self.searchAnimation.expand()
else:
self.searchAnimation.collapse()
QTimer.singleShot(0, self._center_collapsed_search_icon)
return handle_focus_event
def _wrap_search_resize_event(self, original_event: Callable) -> Callable:
def handle_resize_event(event):
original_event(event)
self._center_collapsed_search_icon()
return handle_resize_event
def _center_collapsed_search_icon(self) -> None:
animation = getattr(self, "searchAnimation", None)
if animation is None or self.searchEdit.maximumWidth() != animation.collapsed_width:
return
for button in self.searchEdit.findChildren(QToolButton):
if button.defaultAction() is self.searchIconAction:
size = button.sizeHint()
x = (self.searchEdit.width() - size.width()) // 2
y = (self.searchEdit.height() - size.height()) // 2
button.setGeometry(x, y, size.width(), size.height())
return
def _add_library_filter_controls(self, controls_layout: QHBoxLayout) -> None:
self.sort_keys = ["last_launch", "playtime", "alphabetical"]
self.sort_labels = [_("Last launch"), _("Time spent"), _("Alphabetical")]
self.gamesSortCombo = self._create_library_combo(self.sort_labels, _("Sort Method:"))
self._set_combo_current_key(
self.gamesSortCombo,
self.sort_keys,
game_config.get_sort_method(),
)
self.gamesSortCombo.currentIndexChanged.connect(self._on_library_sort_changed)
controls_layout.addStretch()
controls_layout.addWidget(self.gamesSortCombo)
self.filter_keys = ["all", "steam", "portproton", "favorites"]
self.filter_labels = [_("All"), "Steam", "PortProton", _("Favorites")]
self.gamesDisplayCombo = self._create_library_combo(self.filter_labels, _("Display Filter:"))
self._set_combo_current_key(
self.gamesDisplayCombo,
self.filter_keys,
game_config.get_display_filter(),
)
self.gamesDisplayCombo.currentIndexChanged.connect(self._on_library_filter_changed)
controls_layout.addWidget(self.gamesDisplayCombo)
def createSearchWidget(self) -> tuple[QWidget, CustomLineEdit]:
self.container = QWidget()
self.container.setStyleSheet(self.theme.CONTAINER_STYLE)
layout = QVBoxLayout(self.container)
layout.setContentsMargins(0, 6, 0, 0)
layout.setSpacing(10)
buttons_layout = QHBoxLayout()
buttons_layout.setContentsMargins(0, 0, 0, 0)
buttons_layout.setSpacing(10)
controls_layout = self._create_library_controls_widget()
self._add_library_action_buttons(buttons_layout)
self._add_library_search(buttons_layout)
self.libraryControlsAnimation = LibraryControlsAnimation(
self.libraryControlsWidget,
self.theme,
self.searchDebounceTimer.interval(),
)
self._add_library_controls_button(buttons_layout)
self._setup_library_search_animation()
self._add_library_filter_controls(controls_layout)
self.libraryControlsAnimation.setup_hidden()
layout.addLayout(buttons_layout)
layout.addWidget(self.libraryControlsWidget)
return self.container, self.searchEdit
def refreshGames(self):
@@ -2377,56 +2535,6 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
self.timeDetailCombo.setCurrentIndex(idx)
genForm.addRow(self.timeDetailTitle, self.timeDetailCombo)
self.gamesSortCombo = QComboBox()
self.gamesSortCombo.view().window().setWindowFlags(
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
)
self.gamesSortCombo.view().window().setAttribute(
Qt.WidgetAttribute.WA_TranslucentBackground
)
self.gamesSortCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.sort_keys = ["last_launch", "playtime", "alphabetical"]
self.sort_labels = [_("Last launch"), _("Time spent"), _("Alphabetical")]
self.gamesSortCombo.addItems(self.sort_labels)
self.gamesSortCombo.setStyleSheet(self.theme.COMBOBOX_STYLE + self.theme.SCROLL_STYLE)
self.gamesSortCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.gamesSortTitle = QLabel(_("Sort Method:"))
self.gamesSortTitle.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.gamesSortTitle.setStyleSheet(self.theme.SETTINGS_TITLE_STYLE)
self.gamesSortTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
current = game_config.get_sort_method()
try:
idx = self.sort_keys.index(current)
except ValueError:
idx = 0
self.gamesSortCombo.setCurrentIndex(idx)
genForm.addRow(self.gamesSortTitle, self.gamesSortCombo)
self.filter_keys = ["all", "steam", "portproton", "favorites"]
self.filter_labels = [_("All"), "Steam", "PortProton", _("Favorites")]
self.gamesDisplayCombo = QComboBox()
self.gamesDisplayCombo.view().window().setWindowFlags(
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
)
self.gamesDisplayCombo.view().window().setAttribute(
Qt.WidgetAttribute.WA_TranslucentBackground
)
self.gamesDisplayCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.gamesDisplayCombo.addItems(self.filter_labels)
self.gamesDisplayCombo.setStyleSheet(self.theme.COMBOBOX_STYLE + self.theme.SCROLL_STYLE)
self.gamesDisplayCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.gamesDisplayTitle = QLabel(_("Display Filter:"))
self.gamesDisplayTitle.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.gamesDisplayTitle.setStyleSheet(self.theme.SETTINGS_TITLE_STYLE)
self.gamesDisplayTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
current = game_config.get_display_filter()
try:
idx = self.filter_keys.index(current)
except ValueError:
idx = 0
self.gamesDisplayCombo.setCurrentIndex(idx)
genForm.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo)
self.badge_view_keys = ["detailed", "compact", "hidden"]
self.badge_view_labels = [_("Detailed"), _("Compact"), _("Hidden")]
self.badgeViewCombo = QComboBox()
@@ -2904,16 +3012,9 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
time_key = self.time_keys[time_idx]
ui_config.set_time_detail_level(time_key)
sort_idx = self.gamesSortCombo.currentIndex()
sort_key = self.sort_keys[sort_idx]
game_config.set_sort_method(sort_key)
filter_idx = self.gamesDisplayCombo.currentIndex()
filter_key = self.filter_keys[filter_idx]
economy_mode = self.economyModeCheckBox.isChecked()
ui_config.set_economy_mode(economy_mode)
economy_mode_changed = previous_economy_mode != economy_mode
game_config.set_display_filter(filter_key)
badge_view_idx = self.badgeViewCombo.currentIndex()
badge_view_mode = self.badge_view_keys[badge_view_idx]
if economy_mode:
@@ -2989,8 +3090,9 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
if self.game_library_manager.gamesListLayout is not None:
self.game_library_manager.clear_layout(self.game_library_manager.gamesListLayout)
else:
display_filter = game_config.get_display_filter()
for card in self.game_library_manager.game_card_cache.values():
card.update_badge_visibility(filter_key)
card.update_badge_visibility(display_filter)
card.update_badge_view_mode(badge_view_mode)
if (
+1
View File
@@ -38,6 +38,7 @@ install_data(
['animations/__init__.py',
'animations/detail_page.py',
'animations/game_card.py',
'animations/library_controls.py',
'animations/virtual_keyboard.py',
],
install_dir: pythondir / meson.project_name() / 'animations',
@@ -271,6 +271,96 @@ ADDGAME_BACK_BUTTON_STYLE = f"""
}}
"""
LIBRARY_CONTROLS_BUTTON_STYLE = f"""
QPushButton {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #E9ECEF,
stop:1 {color_j}
);
border: {border_b} {color_f};
border-radius: {border_radius_a};
color: {color_l};
font-size: {font_size_a};
font-family: '{font_family}';
padding: 5px 10px;
min-width: 24px;
min-height: 25px;
}}
QPushButton:hover,
QPushButton:pressed,
QPushButton:focus,
QPushButton:checked {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #E9ECEF,
stop:1 {color_j}
);
border: {border_b} {color_a};
}}
"""
LIBRARY_FILTER_COMBOBOX_STYLE = f"""
QComboBox {{
background: {color_b};
border: {border_b} {color_f};
border-radius: {border_radius_a};
padding-left: 12px;
height: 30px;
color: {color_l};
font-family: '{font_family}';
font-size: {font_size_a};
min-width: 120px;
combobox-popup: 0;
}}
QComboBox:hover,
QComboBox:focus,
QComboBox:on {{
background: {color_b};
border: {border_b} {color_a};
}}
QComboBox::drop-down {{
subcontrol-origin: padding;
subcontrol-position: center right;
border-left: {border_b} rgba(255, 255, 255, 0.05);
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow {{
image: url({theme_manager.get_icon("down", current_theme_name, as_path=True)});
padding: 12px;
height: 10px;
width: 10px;
}}
QComboBox::down-arrow:on {{
image: url({theme_manager.get_icon("up", current_theme_name, as_path=True)});
padding: 12px;
height: 10px;
width: 10px;
}}
QComboBox QAbstractItemView {{
outline: none;
background: {color_c};
border: {border_b} {color_a};
border-radius: {border_radius_a};
color: {color_l};
selection-background-color: {color_a};
selection-color: {color_l};
}}
QComboBox QAbstractItemView::item {{
background: {color_c};
color: {color_l};
padding: 6px 10px;
min-height: 24px;
}}
QComboBox QAbstractItemView::item:hover,
QComboBox QAbstractItemView::item:selected {{
background: {color_a};
color: {color_l};
}}
"""
# SEARCH FIELD STYLE
SEARCH_EDIT_STYLE = f"""
QLineEdit {{
+90
View File
@@ -201,6 +201,96 @@ ADDGAME_BACK_BUTTON_STYLE = f"""
}}
"""
LIBRARY_CONTROLS_BUTTON_STYLE = f"""
QPushButton {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #404554,
stop:1 #30323d
);
border: {border_a};
border-radius: {border_radius_a};
color: {color_f};
font-size: {font_size_a};
font-family: '{font_family}';
padding: 5px 10px;
min-width: 24px;
min-height: 25px;
}}
QPushButton:hover,
QPushButton:pressed,
QPushButton:focus,
QPushButton:checked {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #404554,
stop:1 #30323d
);
border: {border_b} {color_a};
}}
"""
LIBRARY_FILTER_COMBOBOX_STYLE = f"""
QComboBox {{
background: {color_b};
border: {border_a};
border-radius: {border_radius_a};
padding-left: 12px;
height: 30px;
color: {color_f};
font-family: '{font_family}';
font-size: {font_size_a};
min-width: 120px;
combobox-popup: 0;
}}
QComboBox:hover,
QComboBox:focus,
QComboBox:on {{
background: {color_b};
border: {border_b} {color_a};
}}
QComboBox::drop-down {{
subcontrol-origin: padding;
subcontrol-position: center right;
border-left: {border_b} rgba(255, 255, 255, 0.05);
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow {{
image: url({theme_manager.get_icon("down", current_theme_name, as_path=True)});
padding: 12px;
height: 10px;
width: 10px;
}}
QComboBox::down-arrow:on {{
image: url({theme_manager.get_icon("up", current_theme_name, as_path=True)});
padding: 12px;
height: 10px;
width: 10px;
}}
QComboBox QAbstractItemView {{
outline: none;
background: {color_c};
border: {border_b} {color_a};
border-radius: {border_radius_a};
color: {color_f};
selection-background-color: {color_a};
selection-color: {color_f};
}}
QComboBox QAbstractItemView::item {{
background: {color_c};
color: {color_f};
padding: 6px 10px;
min-height: 24px;
}}
QComboBox QAbstractItemView::item:hover,
QComboBox QAbstractItemView::item:selected {{
background: {color_a};
color: {color_f};
}}
"""
# SEARCH FIELD STYLE
SEARCH_EDIT_STYLE = f"""
QLineEdit {{
@@ -788,6 +788,96 @@ ADDGAME_BACK_BUTTON_STYLE = f"""
}}
"""
LIBRARY_CONTROLS_BUTTON_STYLE = f"""
QPushButton {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #E9ECEF,
stop:1 {color_j}
);
border: {border_b} {color_f};
border-radius: {border_radius_a};
color: {color_l};
font-size: {font_size_a};
font-family: '{font_family}';
padding: 5px 10px;
min-width: 24px;
min-height: 25px;
}}
QPushButton:hover,
QPushButton:pressed,
QPushButton:focus,
QPushButton:checked {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 #E9ECEF,
stop:1 {color_j}
);
border: {border_b} {color_a};
}}
"""
LIBRARY_FILTER_COMBOBOX_STYLE = f"""
QComboBox {{
background: {color_b};
border: {border_b} {color_f};
border-radius: {border_radius_a};
padding-left: 12px;
height: 34px;
color: {color_l};
font-family: '{font_family}';
font-size: {font_size_a};
min-width: 120px;
combobox-popup: 0;
}}
QComboBox:hover,
QComboBox:focus,
QComboBox:on {{
background: {color_b};
border: {border_b} {color_a};
}}
QComboBox::drop-down {{
subcontrol-origin: padding;
subcontrol-position: center right;
border-left: {border_b} rgba(255, 255, 255, 0.05);
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow {{
image: url({theme_manager.get_icon("down", current_theme_name, as_path=True)});
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow:on {{
image: url({theme_manager.get_icon("up", current_theme_name, as_path=True)});
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox QAbstractItemView {{
outline: none;
background: {color_c};
border: {border_b} {color_a};
border-radius: {border_radius_a};
color: {color_l};
selection-background-color: {color_a};
selection-color: {color_l};
}}
QComboBox QAbstractItemView::item {{
background: {color_c};
color: {color_l};
padding: 6px 10px;
min-height: 24px;
}}
QComboBox QAbstractItemView::item:hover,
QComboBox QAbstractItemView::item:selected {{
background: {color_a};
color: {color_l};
}}
"""
# SEARCH FIELD STYLE
SEARCH_EDIT_STYLE = f"""
QLineEdit {{
@@ -26,6 +26,87 @@ ADDGAME_BACK_BUTTON_STYLE = f"""
}}
"""
LIBRARY_CONTROLS_BUTTON_STYLE = f"""
QPushButton {{
background: rgba(20, 20, 20, 0.40);
border: {border_b} rgba(255, 255, 255, 0.5);
border-radius: {border_radius_a};
color: {color_f};
font-size: {font_size_a};
font-family: '{font_family}';
padding: 8px 12px;
min-width: 24px;
}}
QPushButton:hover,
QPushButton:pressed,
QPushButton:focus,
QPushButton:checked {{
background: rgba(20, 20, 20, 0.40);
border: {border_b} {color_a};
}}
"""
LIBRARY_FILTER_COMBOBOX_STYLE = f"""
QComboBox {{
background: rgba(20, 20, 20, 0.40);
border: {border_b} rgba(255, 255, 255, 0.5);
border-radius: {border_radius_a};
padding-left: 12px;
height: 34px;
color: {color_f};
font-family: '{font_family}';
font-size: {font_size_a};
min-width: 120px;
combobox-popup: 0;
}}
QComboBox:hover,
QComboBox:focus,
QComboBox:on {{
background: rgba(20, 20, 20, 0.40);
border: {border_b} {color_a};
}}
QComboBox::drop-down {{
subcontrol-origin: padding;
subcontrol-position: center right;
border-left: {border_b} rgba(255, 255, 255, 0.05);
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow {{
image: url({theme_manager.get_icon("down", current_theme_name, as_path=True)});
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox::down-arrow:on {{
image: url({theme_manager.get_icon("up", current_theme_name, as_path=True)});
padding: 12px;
height: 12px;
width: 12px;
}}
QComboBox QAbstractItemView {{
outline: none;
background: {color_c};
border: {border_b} {color_a};
border-radius: {border_radius_a};
color: {color_f};
selection-background-color: {color_a};
selection-color: {color_f};
}}
QComboBox QAbstractItemView::item {{
background: {color_c};
color: {color_f};
padding: 6px 10px;
min-height: 24px;
}}
QComboBox QAbstractItemView::item:hover,
QComboBox QAbstractItemView::item:selected {{
background: {color_a};
color: {color_f};
}}
"""
# MAIN FRAME FOR GAME DETAILS
DETAIL_CONTENT_FRAME_STYLE = f"""
QFrame {{