7 Commits

Author SHA1 Message Date
b317e4760b feat(build): use CHANGELOG.md for release notes instead of commit history
All checks were successful
Code and build check / Check code (push) Successful in 1m23s
Code and build check / Build with uv (push) Successful in 45s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 20:56:43 +05:00
6d3e0982c9 feat(bump_ver): add changelog version and date update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 20:40:25 +05:00
372832b41d chore(changelog): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 20:36:52 +05:00
58a01d36fb feat(game_card): show source badges only for “all” and “favorites” filters
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 20:34:11 +05:00
5d84dbad8e refactor: rename steam_game to game_source for better clarity
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 20:11:05 +05:00
61964d21c7 feat(ui): add PortProton badge to game cards and detail pages
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 19:57:30 +05:00
2971a594dc feat: add change_cursor parameter to ClickableLabel for EGS
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-04 19:39:24 +05:00
7 changed files with 172 additions and 42 deletions

View File

@@ -145,14 +145,17 @@ jobs:
with: with:
path: release/ path: release/
- name: Get Changes between Tags - name: Extract changelog for version
id: changes id: changelog
uses: https://github.com/simbo/changes-between-tags-action@v1 run: |
VERSION="${{ env.VERSION }}"
VERSION=${VERSION#v} # Remove 'v' prefix if present
awk "/^## \\[$VERSION\\]/ {flag=1; next} /^## \\[/ || /^---/ {flag=0} flag" CHANGELOG.md > changelog.txt
- name: Release - name: Release
uses: https://gitea.com/actions/gitea-release-action@v1 uses: https://gitea.com/actions/gitea-release-action@v1
with: with:
body: ${{ steps.changes.outputs.changes }} body_path: changelog.txt
token: ${{ env.GITEA_TOKEN }} token: ${{ env.GITEA_TOKEN }}
tag_name: ${{ env.VERSION }} tag_name: ${{ env.VERSION }}
prerelease: true prerelease: true

View File

@@ -8,6 +8,8 @@
### Added ### Added
- Кнопки сброса настроек и очистки кэша - Кнопки сброса настроек и очистки кэша
- Начальная интеграция с EGS с помощью [Legendary](https://github.com/derrod/legendary) - Начальная интеграция с EGS с помощью [Legendary](https://github.com/derrod/legendary)
- Бейдж EGS
- Бейдж PortProton
- Зависимость на `xdg-utils` - Зависимость на `xdg-utils`
- Интеграция статуса WeAntiCheatYet в карточку - Интеграция статуса WeAntiCheatYet в карточку
- Стили в AddGameDialog - Стили в AddGameDialog
@@ -25,12 +27,14 @@
### Changed ### Changed
- Обновлены все иконки - Обновлены все иконки
- Переименован `_get_steam_home``get_steam_home` - Переименован `_get_steam_home``get_steam_home`
- Переименован `steam_game``game_source`
- Догика контекстного меню вынесена в `ContextMenuManager` - Догика контекстного меню вынесена в `ContextMenuManager`
- Бейдж Steam теперь открывает Steam Community - Бейдж Steam теперь открывает Steam Community
- Изменена лицензия с MIT на GPL-3.0 для совместимости с кодом от legendary - Изменена лицензия с MIT на GPL-3.0 для совместимости с кодом от legendary
- Оптимизирована генерация карточек для предотвращения лагов при поиске и изменения размера окна - Оптимизирована генерация карточек для предотвращения лагов при поиске и изменения размера окна
- Бейджи с карточек так же теперь дублируются и на странице с деталями, а не только в библиотеке - Бейджи с карточек так же теперь дублируются и на странице с деталями, а не только в библиотеке
- Установка ширины бейджа в две трети ширины карточки - Установка ширины бейджа в две трети ширины карточки
- Бейджи источников (`Steam`, `EGS`, `PortProton`) теперь отображаются только при активном фильтре `all` или `favorites`
### Fixed ### Fixed
- Обработка несуществующей темы с возвратом к “standart” - Обработка несуществующей темы с возвратом к “standart”

View File

@@ -3,6 +3,7 @@
import argparse import argparse
import re import re
from pathlib import Path from pathlib import Path
from datetime import date
# Base directory of the project # Base directory of the project
BASE_DIR = Path(__file__).parent.parent BASE_DIR = Path(__file__).parent.parent
@@ -13,6 +14,7 @@ FEDORA_SPEC = BASE_DIR / "build-aux" / "fedora.spec"
PYPROJECT = BASE_DIR / "pyproject.toml" PYPROJECT = BASE_DIR / "pyproject.toml"
APP_PY = BASE_DIR / "portprotonqt" / "app.py" APP_PY = BASE_DIR / "portprotonqt" / "app.py"
GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml" GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml"
CHANGELOG = BASE_DIR / "CHANGELOG.md"
def bump_appimage(path: Path, old: str, new: str) -> bool: def bump_appimage(path: Path, old: str, new: str) -> bool:
""" """
@@ -27,7 +29,6 @@ def bump_appimage(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_arch(path: Path, old: str, new: str) -> bool: def bump_arch(path: Path, old: str, new: str) -> bool:
""" """
Update pkgver in PKGBUILD Update pkgver in PKGBUILD
@@ -41,7 +42,6 @@ def bump_arch(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_fedora(path: Path, old: str, new: str) -> bool: def bump_fedora(path: Path, old: str, new: str) -> bool:
""" """
Update only the '%global pypi_version' line in fedora.spec Update only the '%global pypi_version' line in fedora.spec
@@ -55,7 +55,6 @@ def bump_fedora(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_pyproject(path: Path, old: str, new: str) -> bool: def bump_pyproject(path: Path, old: str, new: str) -> bool:
""" """
Update version in pyproject.toml under [project] Update version in pyproject.toml under [project]
@@ -69,7 +68,6 @@ def bump_pyproject(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_app_py(path: Path, old: str, new: str) -> bool: def bump_app_py(path: Path, old: str, new: str) -> bool:
""" """
Update __app_version__ in app.py Update __app_version__ in app.py
@@ -83,7 +81,6 @@ def bump_app_py(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_workflow(path: Path, old: str, new: str) -> bool: def bump_workflow(path: Path, old: str, new: str) -> bool:
""" """
Update VERSION in Gitea Actions workflow Update VERSION in Gitea Actions workflow
@@ -97,6 +94,19 @@ def bump_workflow(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_changelog(path: Path, old: str, new: str) -> bool:
"""
Update [Unreleased] to [new] - YYYY-MM-DD in CHANGELOG.md
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(?m)^##\s*\[Unreleased\]$")
current_date = date.today().strftime('%Y-%m-%d')
new_text, count = pattern.subn(f"## [{new}] - {current_date}", text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def main(): def main():
parser = argparse.ArgumentParser(description='Bump project version in specific files') parser = argparse.ArgumentParser(description='Bump project version in specific files')
@@ -111,7 +121,8 @@ def main():
(FEDORA_SPEC, bump_fedora), (FEDORA_SPEC, bump_fedora),
(PYPROJECT, bump_pyproject), (PYPROJECT, bump_pyproject),
(APP_PY, bump_app_py), (APP_PY, bump_app_py),
(GITEA_WORKFLOW, bump_workflow) (GITEA_WORKFLOW, bump_workflow),
(CHANGELOG, bump_changelog)
] ]
updated = [] updated = []
@@ -126,6 +137,5 @@ def main():
else: else:
print(f"No occurrences of version {old} found in specified files.") print(f"No occurrences of version {old} found in specified files.")
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -40,7 +40,7 @@ class ContextMenuManager:
""" """
menu = QMenu(self.parent) menu = QMenu(self.parent)
if game_card.steam_game != "true": if game_card.game_source not in ("steam", "epic"):
desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip() desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip()
desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop") desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop")
if os.path.exists(desktop_path): if os.path.exists(desktop_path):

View File

@@ -133,7 +133,7 @@ class FlowLayout(QLayout):
class ClickableLabel(QLabel): class ClickableLabel(QLabel):
clicked = Signal() clicked = Signal()
def __init__(self, *args, icon=None, icon_size=16, icon_space=5, **kwargs): def __init__(self, *args, icon=None, icon_size=16, icon_space=5, change_cursor=True, **kwargs):
""" """
Поддерживаются вызовы: Поддерживаются вызовы:
- ClickableLabel("текст", parent=...) первый аргумент строка, - ClickableLabel("текст", parent=...) первый аргумент строка,
@@ -143,6 +143,7 @@ class ClickableLabel(QLabel):
icon: QIcon или None иконка, которая будет отрисована вместе с текстом. icon: QIcon или None иконка, которая будет отрисована вместе с текстом.
icon_size: int размер иконки (ширина и высота). icon_size: int размер иконки (ширина и высота).
icon_space: int отступ между иконкой и текстом. icon_space: int отступ между иконкой и текстом.
change_cursor: bool изменять ли курсор на PointingHandCursor при наведении (по умолчанию True).
""" """
if args and isinstance(args[0], str): if args and isinstance(args[0], str):
text = args[0] text = args[0]
@@ -161,7 +162,8 @@ class ClickableLabel(QLabel):
self._icon = icon self._icon = icon
self._icon_size = icon_size self._icon_size = icon_size
self._icon_space = icon_space self._icon_space = icon_space
self.setCursor(Qt.CursorShape.PointingHandCursor) if change_cursor:
self.setCursor(Qt.CursorShape.PointingHandCursor)
def setIcon(self, icon): def setIcon(self, icon):
"""Устанавливает иконку и перерисовывает виджет.""" """Устанавливает иконку и перерисовывает виджет."""

View File

@@ -5,7 +5,7 @@ from collections.abc import Callable
import portprotonqt.themes.standart.styles as default_styles import portprotonqt.themes.standart.styles as default_styles
from portprotonqt.image_utils import load_pixmap_async, round_corners from portprotonqt.image_utils import load_pixmap_async, round_corners
from portprotonqt.localization import _ from portprotonqt.localization import _
from portprotonqt.config_utils import read_favorites, save_favorites from portprotonqt.config_utils import read_favorites, save_favorites, read_display_filter
from portprotonqt.theme_manager import ThemeManager from portprotonqt.theme_manager import ThemeManager
from portprotonqt.config_utils import read_theme_from_config from portprotonqt.config_utils import read_theme_from_config
from portprotonqt.custom_widgets import ClickableLabel from portprotonqt.custom_widgets import ClickableLabel
@@ -27,7 +27,7 @@ class GameCard(QFrame):
openGameFolderRequested = Signal(str, str) # name, exec_line openGameFolderRequested = Signal(str, str) # name, exec_line
def __init__(self, name, description, cover_path, appid, controller_support, exec_line, def __init__(self, name, description, cover_path, appid, controller_support, exec_line,
last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, steam_game, last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source,
select_callback, theme=None, card_width=250, parent=None, context_menu_manager=None): select_callback, theme=None, card_width=250, parent=None, context_menu_manager=None):
super().__init__(parent) super().__init__(parent)
self.name = name self.name = name
@@ -40,7 +40,7 @@ class GameCard(QFrame):
self.formatted_playtime = formatted_playtime self.formatted_playtime = formatted_playtime
self.protondb_tier = protondb_tier self.protondb_tier = protondb_tier
self.anticheat_status = anticheat_status self.anticheat_status = anticheat_status
self.steam_game = steam_game self.game_source = game_source
self.last_launch_ts = last_launch_ts self.last_launch_ts = last_launch_ts
self.playtime_seconds = playtime_seconds self.playtime_seconds = playtime_seconds
@@ -51,6 +51,7 @@ class GameCard(QFrame):
self.theme_manager = ThemeManager() self.theme_manager = ThemeManager()
self.theme = theme if theme is not None else default_styles self.theme = theme if theme is not None else default_styles
self.display_filter = read_display_filter()
self.current_theme_name = read_theme_from_config() self.current_theme_name = read_theme_from_config()
# Дополнительное пространство для анимации # Дополнительное пространство для анимации
@@ -105,7 +106,6 @@ class GameCard(QFrame):
def on_cover_loaded(pixmap): def on_cover_loaded(pixmap):
label = label_ref() label = label_ref()
if label is None: if label is None:
# QLabel уже удалён — ничего не делаем
return return
label.setPixmap(round_corners(pixmap, 15)) label.setPixmap(round_corners(pixmap, 15))
@@ -121,6 +121,10 @@ class GameCard(QFrame):
self.update_favorite_icon() self.update_favorite_icon()
self.favoriteLabel.raise_() self.favoriteLabel.raise_()
steam_visible = (str(game_source).lower() == "steam" and self.display_filter in ("all", "favorites"))
egs_visible = (str(game_source).lower() == "epic" and self.display_filter in ("all", "favorites"))
portproton_visible = (str(game_source).lower() == "portproton" and self.display_filter in ("all", "favorites"))
# ProtonDB бейдж # ProtonDB бейдж
tier_text = self.getProtonDBText(protondb_tier) tier_text = self.getProtonDBText(protondb_tier)
if tier_text: if tier_text:
@@ -134,11 +138,11 @@ class GameCard(QFrame):
icon_space=3, icon_space=3,
) )
self.protondbLabel.setStyleSheet(self.theme.get_protondb_badge_style(protondb_tier)) self.protondbLabel.setStyleSheet(self.theme.get_protondb_badge_style(protondb_tier))
self.protondbLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину в 2/3 ширины карточки self.protondbLabel.setFixedWidth(int(card_width * 2/3))
protondb_visible = True protondb_visible = True
else: else:
self.protondbLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3) self.protondbLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3)
self.protondbLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину даже для невидимого бейджа self.protondbLabel.setFixedWidth(int(card_width * 2/3))
self.protondbLabel.setVisible(False) self.protondbLabel.setVisible(False)
protondb_visible = False protondb_visible = False
@@ -152,8 +156,7 @@ class GameCard(QFrame):
icon_space=5, icon_space=5,
) )
self.steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE) self.steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
self.steamLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину в 2/3 ширины карточки self.steamLabel.setFixedWidth(int(card_width * 2/3))
steam_visible = (str(steam_game).lower() == "true")
self.steamLabel.setVisible(steam_visible) self.steamLabel.setVisible(steam_visible)
# Epic Games Store бейдж # Epic Games Store бейдж
@@ -164,12 +167,26 @@ class GameCard(QFrame):
parent=coverWidget, parent=coverWidget,
icon_size=16, icon_size=16,
icon_space=5, icon_space=5,
change_cursor=False
) )
self.egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE) self.egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
self.egsLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину в 2/3 ширины карточки self.egsLabel.setFixedWidth(int(card_width * 2/3))
egs_visible = (str(steam_game).lower() == "epic")
self.egsLabel.setVisible(egs_visible) self.egsLabel.setVisible(egs_visible)
# PortProton badge
portproton_icon = self.theme_manager.get_icon("ppqt-tray")
self.portprotonLabel = ClickableLabel(
"PortProton",
icon=portproton_icon,
parent=coverWidget,
icon_size=16,
icon_space=5,
change_cursor=False
)
self.portprotonLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
self.portprotonLabel.setFixedWidth(int(card_width * 2/3))
self.portprotonLabel.setVisible(portproton_visible)
# WeAntiCheatYet бейдж # WeAntiCheatYet бейдж
anticheat_text = self.getAntiCheatText(anticheat_status) anticheat_text = self.getAntiCheatText(anticheat_status)
if anticheat_text: if anticheat_text:
@@ -183,11 +200,11 @@ class GameCard(QFrame):
icon_space=3, icon_space=3,
) )
self.anticheatLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE) self.anticheatLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
self.anticheatLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину в 2/3 ширины карточки self.anticheatLabel.setFixedWidth(int(card_width * 2/3))
anticheat_visible = True anticheat_visible = True
else: else:
self.anticheatLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3) self.anticheatLabel = ClickableLabel("", parent=coverWidget, icon_size=16, icon_space=3)
self.anticheatLabel.setFixedWidth(int(card_width * 2/3)) # Устанавливаем ширину даже для невидимого бейджа self.anticheatLabel.setFixedWidth(int(card_width * 2/3))
self.anticheatLabel.setVisible(False) self.anticheatLabel.setVisible(False)
anticheat_visible = False anticheat_visible = False
@@ -196,7 +213,7 @@ class GameCard(QFrame):
badge_spacing = 5 badge_spacing = 5
top_y = 10 top_y = 10
badge_y_positions = [] badge_y_positions = []
badge_width = int(card_width * 2/3) # Фиксированная ширина бейджей badge_width = int(card_width * 2/3)
if steam_visible: if steam_visible:
steam_x = card_width - badge_width - right_margin steam_x = card_width - badge_width - right_margin
self.steamLabel.move(steam_x, top_y) self.steamLabel.move(steam_x, top_y)
@@ -206,6 +223,11 @@ class GameCard(QFrame):
egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.egsLabel.move(egs_x, egs_y) self.egsLabel.move(egs_x, egs_y)
badge_y_positions.append(egs_y + self.egsLabel.height()) badge_y_positions.append(egs_y + self.egsLabel.height())
if portproton_visible:
portproton_x = card_width - badge_width - right_margin
portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.portprotonLabel.move(portproton_x, portproton_y)
badge_y_positions.append(portproton_y + self.portprotonLabel.height())
if protondb_visible: if protondb_visible:
protondb_x = card_width - badge_width - right_margin protondb_x = card_width - badge_width - right_margin
protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
@@ -218,6 +240,7 @@ class GameCard(QFrame):
self.anticheatLabel.raise_() self.anticheatLabel.raise_()
self.protondbLabel.raise_() self.protondbLabel.raise_()
self.portprotonLabel.raise_()
self.egsLabel.raise_() self.egsLabel.raise_()
self.steamLabel.raise_() self.steamLabel.raise_()
self.protondbLabel.clicked.connect(self.open_protondb_report) self.protondbLabel.clicked.connect(self.open_protondb_report)
@@ -232,6 +255,53 @@ class GameCard(QFrame):
nameLabel.setStyleSheet(self.theme.GAME_CARD_NAME_LABEL_STYLE) nameLabel.setStyleSheet(self.theme.GAME_CARD_NAME_LABEL_STYLE)
layout.addWidget(nameLabel) layout.addWidget(nameLabel)
def update_badge_visibility(self, display_filter: str):
"""Update badge visibility based on the provided display_filter."""
self.display_filter = display_filter
self.steam_visible = (str(self.game_source).lower() == "steam" and display_filter in ("all", "favorites"))
self.egs_visible = (str(self.game_source).lower() == "epic" and display_filter in ("all", "favorites"))
self.portproton_visible = (str(self.game_source).lower() == "portproton" and display_filter in ("all", "favorites"))
self.steamLabel.setVisible(self.steam_visible)
self.egsLabel.setVisible(self.egs_visible)
self.portprotonLabel.setVisible(self.portproton_visible)
# Reposition badges
right_margin = 8
badge_spacing = 5
top_y = 10
badge_y_positions = []
badge_width = int(self.coverLabel.width() * 2/3)
if self.steam_visible:
steam_x = self.coverLabel.width() - badge_width - right_margin
self.steamLabel.move(steam_x, top_y)
badge_y_positions.append(top_y + self.steamLabel.height())
if self.egs_visible:
egs_x = self.coverLabel.width() - badge_width - right_margin
egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.egsLabel.move(egs_x, egs_y)
badge_y_positions.append(egs_y + self.egsLabel.height())
if self.portproton_visible:
portproton_x = self.coverLabel.width() - badge_width - right_margin
portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.portprotonLabel.move(portproton_x, portproton_y)
badge_y_positions.append(portproton_y + self.portprotonLabel.height())
if self.protondbLabel.isVisible():
protondb_x = self.coverLabel.width() - badge_width - right_margin
protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.protondbLabel.move(protondb_x, protondb_y)
badge_y_positions.append(protondb_y + self.protondbLabel.height())
if self.anticheatLabel.isVisible():
anticheat_x = self.coverLabel.width() - badge_width - right_margin
anticheat_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
self.anticheatLabel.move(anticheat_x, anticheat_y)
self.anticheatLabel.raise_()
self.protondbLabel.raise_()
self.portprotonLabel.raise_()
self.egsLabel.raise_()
self.steamLabel.raise_()
def _show_context_menu(self, pos): def _show_context_menu(self, pos):
"""Delegate context menu display to ContextMenuManager.""" """Delegate context menu display to ContextMenuManager."""
if self.context_menu_manager: if self.context_menu_manager:
@@ -475,7 +545,7 @@ class GameCard(QFrame):
self.last_launch, self.last_launch,
self.formatted_playtime, self.formatted_playtime,
self.protondb_tier, self.protondb_tier,
self.steam_game, self.game_source,
self.anticheat_status self.anticheat_status
) )
super().mousePressEvent(event) super().mousePressEvent(event)
@@ -492,7 +562,7 @@ class GameCard(QFrame):
self.last_launch, self.last_launch,
self.formatted_playtime, self.formatted_playtime,
self.protondb_tier, self.protondb_tier,
self.steam_game, self.game_source,
self.anticheat_status self.anticheat_status
) )
else: else:

View File

@@ -314,7 +314,7 @@ class MainWindow(QMainWindow):
'controller_support': '', 'controller_support': '',
'protondb_tier': '', 'protondb_tier': '',
'name': name, 'name': name,
'steam_game': 'true' 'game_source': 'steam'
} }
last_launch = format_last_launch(datetime.fromtimestamp(last_played)) if last_played else _("Never") last_launch = format_last_launch(datetime.fromtimestamp(last_played)) if last_played else _("Never")
steam_games.append(( steam_games.append((
@@ -330,7 +330,7 @@ class MainWindow(QMainWindow):
info.get("anticheat_status", ""), info.get("anticheat_status", ""),
last_played, last_played,
playtime_seconds, playtime_seconds,
"true" "steam"
)) ))
processed_count += 1 processed_count += 1
self.pending_games.append(None) self.pending_games.append(None)
@@ -462,7 +462,6 @@ class MainWindow(QMainWindow):
final_cover = (user_cover if user_cover else final_cover = (user_cover if user_cover else
builtin_cover if builtin_cover else builtin_cover if builtin_cover else
steam_info.get("cover", "") or entry.get("Icon", "")) steam_info.get("cover", "") or entry.get("Icon", ""))
steam_game = "false"
callback(( callback((
final_name, final_name,
final_desc, final_desc,
@@ -476,7 +475,7 @@ class MainWindow(QMainWindow):
steam_info.get("anticheat_status", ""), steam_info.get("anticheat_status", ""),
get_last_launch_timestamp(exe_name) if exe_name else 0, get_last_launch_timestamp(exe_name) if exe_name else 0,
playtime_seconds, playtime_seconds,
steam_game "portproton"
)) ))
get_steam_game_info_async(desktop_name, exec_line, on_steam_info) get_steam_game_info_async(desktop_name, exec_line, on_steam_info)
@@ -1109,10 +1108,13 @@ class MainWindow(QMainWindow):
self.statusBar().showMessage(_("Cache cleared"), 3000) self.statusBar().showMessage(_("Cache cleared"), 3000)
def applySettingsDelayed(self): def applySettingsDelayed(self):
"""Применяет настройки с учетом нового фильтра и обновляет список игр.""" """Applies settings with the new filter and updates the game list."""
read_time_config() read_time_config()
self.games = [] # Очищаем текущий список игр self.games = []
self.loadGames() # Загружаем игры с новым фильтром self.loadGames()
display_filter = read_display_filter()
for card in self.game_card_cache.values():
card.update_badge_visibility(display_filter)
def savePortProtonSettings(self): def savePortProtonSettings(self):
""" """
@@ -1139,7 +1141,17 @@ class MainWindow(QMainWindow):
fullscreen = self.fullscreenCheckBox.isChecked() fullscreen = self.fullscreenCheckBox.isChecked()
save_fullscreen_config(fullscreen) save_fullscreen_config(fullscreen)
# Запускаем отложенное применение настроек через таймер for card in self.game_card_cache.values():
card.update_badge_visibility(filter_key)
if self.currentDetailPage and self.current_exec_line:
current_game = next((game for game in self.games if game[4] == self.current_exec_line), None)
if current_game:
self.stackedWidget.removeWidget(self.currentDetailPage)
self.currentDetailPage.deleteLater()
self.currentDetailPage = None
self.openGameDetailPage(*current_game)
self.settingsDebounceTimer.start() self.settingsDebounceTimer.start()
self.settings_saved.emit() self.settings_saved.emit()
@@ -1320,7 +1332,7 @@ class MainWindow(QMainWindow):
def darkenColor(self, color, factor=200): def darkenColor(self, color, factor=200):
return color.darker(factor) return color.darker(factor)
def openGameDetailPage(self, name, description, cover_path=None, appid="", exec_line="", controller_support="", last_launch="", formatted_playtime="", protondb_tier="", steam_game="", anticheat_status=""): def openGameDetailPage(self, name, description, cover_path=None, appid="", exec_line="", controller_support="", last_launch="", formatted_playtime="", protondb_tier="", game_source="", anticheat_status=""):
detailPage = QWidget() detailPage = QWidget()
self._animations = {} self._animations = {}
imageLabel = QLabel() imageLabel = QLabel()
@@ -1388,7 +1400,11 @@ class MainWindow(QMainWindow):
favoriteLabelCover.move(8, 8) favoriteLabelCover.move(8, 8)
favoriteLabelCover.raise_() favoriteLabelCover.raise_()
# Добавляем бейджи (ProtonDB, Steam, WeAntiCheatYet) # Добавляем бейджи (ProtonDB, Steam, PortProton, WeAntiCheatYet)
display_filter = read_display_filter()
steam_visible = (str(game_source).lower() == "steam" and display_filter in ("all", "favorites"))
egs_visible = (str(game_source).lower() == "epic" and display_filter in ("all", "favorites"))
portproton_visible = (str(game_source).lower() == "portproton" and display_filter in ("all", "favorites"))
right_margin = 8 right_margin = 8
badge_spacing = 5 badge_spacing = 5
top_y = 10 top_y = 10
@@ -1428,7 +1444,6 @@ class MainWindow(QMainWindow):
) )
steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE) steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
steamLabel.setFixedWidth(badge_width) steamLabel.setFixedWidth(badge_width)
steam_visible = (str(steam_game).lower() == "true")
steamLabel.setVisible(steam_visible) steamLabel.setVisible(steam_visible)
steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}"))) steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}")))
@@ -1440,12 +1455,26 @@ class MainWindow(QMainWindow):
parent=coverFrame, parent=coverFrame,
icon_size=16, icon_size=16,
icon_space=5, icon_space=5,
change_cursor=False
) )
egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE) egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
egsLabel.setFixedWidth(badge_width) egsLabel.setFixedWidth(badge_width)
egs_visible = (str(steam_game).lower() == "epic")
egsLabel.setVisible(egs_visible) egsLabel.setVisible(egs_visible)
# PortProton badge
portproton_icon = self.theme_manager.get_icon("ppqt-tray")
portprotonLabel = ClickableLabel(
"PortProton",
icon=portproton_icon,
parent=coverFrame,
icon_size=16,
icon_space=5,
change_cursor=False
)
portprotonLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
portprotonLabel.setFixedWidth(badge_width)
portprotonLabel.setVisible(portproton_visible)
# WeAntiCheatYet бейдж # WeAntiCheatYet бейдж
anticheat_text = GameCard.getAntiCheatText(anticheat_status) anticheat_text = GameCard.getAntiCheatText(anticheat_status)
if anticheat_text: if anticheat_text:
@@ -1469,6 +1498,11 @@ class MainWindow(QMainWindow):
anticheat_visible = False anticheat_visible = False
# Расположение бейджей # Расположение бейджей
right_margin = 8
badge_spacing = 5
top_y = 10
badge_y_positions = []
badge_width = int(300 * 2/3)
if steam_visible: if steam_visible:
steam_x = 300 - badge_width - right_margin steam_x = 300 - badge_width - right_margin
steamLabel.move(steam_x, top_y) steamLabel.move(steam_x, top_y)
@@ -1478,6 +1512,11 @@ class MainWindow(QMainWindow):
egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
egsLabel.move(egs_x, egs_y) egsLabel.move(egs_x, egs_y)
badge_y_positions.append(egs_y + egsLabel.height()) badge_y_positions.append(egs_y + egsLabel.height())
if portproton_visible:
portproton_x = 300 - badge_width - right_margin
portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
portprotonLabel.move(portproton_x, portproton_y)
badge_y_positions.append(portproton_y + portprotonLabel.height())
if protondb_visible: if protondb_visible:
protondb_x = 300 - badge_width - right_margin protondb_x = 300 - badge_width - right_margin
protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
@@ -1490,6 +1529,8 @@ class MainWindow(QMainWindow):
anticheatLabel.raise_() anticheatLabel.raise_()
protondbLabel.raise_() protondbLabel.raise_()
portprotonLabel.raise_()
egsLabel.raise_()
steamLabel.raise_() steamLabel.raise_()
contentFrameLayout.addWidget(coverFrame) contentFrameLayout.addWidget(coverFrame)
@@ -1637,7 +1678,7 @@ class MainWindow(QMainWindow):
focused_widget.last_launch, focused_widget.last_launch,
focused_widget.formatted_playtime, focused_widget.formatted_playtime,
focused_widget.protondb_tier, focused_widget.protondb_tier,
focused_widget.steam_game focused_widget.game_source
) )
def goBackDetailPage(self, page: QWidget | None) -> None: def goBackDetailPage(self, page: QWidget | None) -> None: