3 Commits

Author SHA1 Message Date
9c4ad0b7ba chore(changelog): update
All checks were successful
Code and build check / Check code (push) Successful in 1m21s
Code and build check / Build with uv (push) Successful in 46s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-07 15:28:41 +05:00
0f59c46d36 fix(input_manager): handle AddGameDialog navigation with D-pad
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-07 15:26:37 +05:00
364e1dd02a feat(input_manager): Added QComboBox and QListView handler for Gamepad
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-07 15:16:42 +05:00
4 changed files with 85 additions and 10 deletions

View File

@@ -24,6 +24,7 @@
- Пункт в контекстное меню "Удалить из Steam”
- Метод сортировки сначала избранное
- Настройка автоматического перехода в режим полноэкранного отображения приложения при подключении геймпада (по умолчанию отключено)
- Обработчики для QMenu и QComboBox на геймпаде
### Changed
- Обновлены все иконки
@@ -39,6 +40,7 @@
- Карточки теперь фокусируются в направлении движения стрелок или D-pad, например если нажать D-pad вниз то перейдёшь на карточку со следующей колонки, а не по порядку
- D-pad больше не переключает вкладки только RB и LB
- Кнопка добавления игры больше не фокусируется
- Диалог добавления игры теперь открывается только в библиотеке
### Fixed
- Обработка несуществующей темы с возвратом к “standart”

View File

@@ -9,6 +9,7 @@ from PySide6.QtGui import QDesktopServices
from portprotonqt.config_utils import parse_desktop_entry
from portprotonqt.localization import _
from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
from portprotonqt.dialogs import AddGameDialog
class ContextMenuManager:
"""Manages context menu actions for game management in PortProtonQT."""
@@ -321,7 +322,6 @@ class ContextMenuManager:
def edit_game_shortcut(self, game_name, exec_line, cover_path):
"""Opens the AddGameDialog in edit mode to modify an existing .desktop file."""
from portprotonqt.dialogs import AddGameDialog # Local import to avoid circular dependency
if not self._check_portproton():
return

View File

@@ -3,7 +3,7 @@ import threading
from typing import Protocol, cast
from evdev import InputDevice, ecodes, list_devices
import pyudev
from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu
from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView
from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot
from PySide6.QtGui import QKeyEvent
from portprotonqt.logger import get_logger
@@ -134,20 +134,55 @@ class InputManager(QObject):
# Handle QMenu (context menu)
if isinstance(popup, QMenu):
if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
# Trigger the currently highlighted menu action and close the menu
if popup.activeAction():
popup.activeAction().trigger()
popup.close()
return
elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
# Close the menu
popup.close()
return
return
# Handle QComboBox
if isinstance(focused, QComboBox):
if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
focused.showPopup()
return
# Handle QListView
if isinstance(focused, QListView):
combo = None
parent = focused.parentWidget()
while parent:
if isinstance(parent, QComboBox):
combo = parent
break
parent = parent.parentWidget()
if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
idx = focused.currentIndex()
if idx.isValid():
if combo:
combo.setCurrentIndex(idx.row())
combo.hidePopup()
combo.setFocus(Qt.FocusReason.OtherFocusReason)
else:
focused.activated.emit(idx)
focused.clicked.emit(idx)
focused.hide()
return
if button_code in BUTTONS['back']:
if combo:
combo.hidePopup()
combo.setFocus(Qt.FocusReason.OtherFocusReason)
else:
focused.clearSelection()
focused.hide()
# Закрытие AddGameDialog на кнопку B
if button_code in BUTTONS['back'] and isinstance(active, QDialog):
active.reject() # Закрываем диалог
active.reject()
return
# FullscreenDialog
@@ -181,6 +216,8 @@ class InputManager(QObject):
elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
self._parent.goBackDetailPage(getattr(self._parent, 'currentDetailPage', None))
elif button_code in BUTTONS['add_game']:
# Only open AddGameDialog if in library tab (index 0)
if self._parent.stackedWidget.currentIndex() == 0:
self._parent.openAddGameDialog()
elif button_code in BUTTONS['prev_tab']:
idx = (self._parent.stackedWidget.currentIndex() - 1) % len(self._parent.tabButtons)
@@ -193,7 +230,6 @@ class InputManager(QObject):
except Exception as e:
logger.error(f"Error in handle_button_slot: {e}", exc_info=True)
@Slot(int, int, float)
def handle_dpad_slot(self, code: int, value: int, current_time: float) -> None:
try:
@@ -205,8 +241,24 @@ class InputManager(QObject):
if not app:
return
active = QApplication.activeWindow()
focused = QApplication.focusWidget()
popup = QApplication.activePopupWidget()
# Handle AddGameDialog navigation with D-pad
if isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:
if not focused or not active.focusWidget():
# If no widget is focused, focus the first focusable widget
focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
if focusables:
focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
return
if value > 0: # Down
active.focusNextChild()
elif value < 0: # Up
active.focusPreviousChild()
return
# Handle QMenu navigation with D-pad
if isinstance(popup, QMenu):
if code == ecodes.ABS_HAT0Y and value != 0:
@@ -222,6 +274,22 @@ class InputManager(QObject):
return
return
# Handle QListView navigation with D-pad
if isinstance(focused, QListView) and code == ecodes.ABS_HAT0Y and value != 0:
model = focused.model()
current_index = focused.currentIndex()
if model and current_index.isValid():
row_count = model.rowCount()
current_row = current_index.row()
if value > 0: # Down
next_row = min(current_row + 1, row_count - 1)
focused.setCurrentIndex(model.index(next_row, current_index.column()))
elif value < 0: # Up
prev_row = max(current_row - 1, 0)
focused.setCurrentIndex(model.index(prev_row, current_index.column()))
focused.scrollTo(focused.currentIndex(), QListView.ScrollHint.PositionAtCenter)
return
# Fullscreen horizontal navigation
if isinstance(active, FullscreenDialog) and code == ecodes.ABS_HAT0X:
if value < 0:
@@ -313,7 +381,6 @@ class InputManager(QObject):
next_card.setFocus()
if scroll_area:
scroll_area.ensureWidgetVisible(next_card, 50, 50)
elif code == ecodes.ABS_HAT0Y and value != 0: # Up/Down
if value > 0: # Down
next_row_idx = current_row_idx + 1
@@ -570,6 +637,9 @@ class InputManager(QObject):
if focusables:
focusables[0].setFocus()
return True
elif focused:
focused.focusNextChild()
return True
# Navigate up through tab content
if key == Qt.Key.Key_Up:
if isinstance(focused, NavLabel):
@@ -590,6 +660,8 @@ class InputManager(QObject):
elif key == Qt.Key.Key_E:
if isinstance(focused, QLineEdit):
return False
# Only open AddGameDialog if in library tab (index 0)
if self._parent.stackedWidget.currentIndex() == 0:
self._parent.openAddGameDialog()
return True

View File

@@ -742,6 +742,7 @@ class MainWindow(QMainWindow):
return
dialog = AddGameDialog(self, self.theme)
dialog.setFocus(Qt.FocusReason.OtherFocusReason)
self.current_add_game_dialog = dialog # Сохраняем ссылку на диалог
# Предзаполняем путь к .exe при drag-and-drop