forked from Boria138/PortProtonQt
		
	Compare commits
	
		
			12 Commits
		
	
	
		
			a3d7351e16
			...
			3d2d5a6243
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3d2d5a6243 | |||
| 565dc49f36 | |||
| c460737bed | |||
| 636ab73580 | |||
| 93954abf0d | |||
| 9ab0adf676 | |||
| c08e4fb38d | |||
| c25589ac96 | |||
| 60d6f0734d | |||
| 57d499fab2 | |||
| bc91b03843 | |||
| aabf8cb30f | 
| @@ -15,6 +15,7 @@ | ||||
| - Стили в  AddGameDialog | ||||
| - Переключение полноэкранного режима через F11 | ||||
| - Выбор QCheckBox через Enter или кнопку A геймпада | ||||
| - Закрытие диалога добавления игры через ESC или кнопку B геймпада | ||||
| - Закрытие окна приложения по комбинации клавиш Ctrl+Q | ||||
| - Сохранение и восстановление размера при рестарте | ||||
| - Переключатель полноэкранного режима приложения | ||||
| @@ -36,6 +37,9 @@ | ||||
| - Бейджи с карточек так же теперь дублируются и на странице с деталями, а не только в библиотеке | ||||
| - Установка ширины бейджа в две трети ширины карточки | ||||
| - Бейджи источников (`Steam`, `EGS`, `PortProton`) теперь отображаются только при активном фильтре `all` или `favorites` | ||||
| - Карточки теперь фокусируются в направлении движения стрелок или D-pad, например если нажать D-pad вниз то перейдёшь на карточку со следующей колонки, а не по порядку | ||||
| - D-pad больше не переключает вкладки только RB и LB | ||||
| - Кнопка добавления игры больше не фокусируется | ||||
|  | ||||
| ### Fixed | ||||
| - Обработка несуществующей темы с возвратом к “standart” | ||||
| @@ -47,6 +51,8 @@ | ||||
| - traceback при загрузке placeholder при отсутствии обложек | ||||
| - Утечки памяти при загрузке обложек | ||||
| - Ошибки при подключении геймпада из-за работы в разных потоках | ||||
| - Множественное открытие диалога добавления игры на геймпаде | ||||
| - Перехват событий геймпада во время работы игры | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog | ||||
| from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot | ||||
| from PySide6.QtGui import QKeyEvent | ||||
| from portprotonqt.logger import get_logger | ||||
| @@ -30,14 +30,15 @@ class MainWindowProtocol(Protocol): | ||||
|     gamesListWidget: QWidget | ||||
|     currentDetailPage: QWidget | None | ||||
|     current_exec_line: str | None | ||||
|     current_add_game_dialog: QDialog | None | ||||
|  | ||||
| # Mapping of actions to evdev button codes, includes PlayStation, Xbox, and Switch controllers | ||||
| BUTTONS = { | ||||
|     'confirm':   {ecodes.BTN_A}, | ||||
|     'back':      {ecodes.BTN_B}, | ||||
|     'add_game':  {ecodes.BTN_Y}, | ||||
|     'prev_tab':  {ecodes.BTN_TL,    ecodes.BTN_TL2}, | ||||
|     'next_tab':  {ecodes.BTN_TR,    ecodes.BTN_TR2}, | ||||
|     'prev_tab':  {ecodes.BTN_TL, ecodes.BTN_TRIGGER_HAPPY7}, | ||||
|     'next_tab':  {ecodes.BTN_TR, ecodes.BTN_TRIGGER_HAPPY5}, | ||||
|     'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR}, | ||||
|     'context_menu': {ecodes.BTN_START}, | ||||
|     'menu':      {ecodes.BTN_SELECT, ecodes.BTN_MODE}, | ||||
| @@ -67,6 +68,7 @@ class InputManager(QObject): | ||||
|         # Ensure attributes exist on main_window | ||||
|         self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None) | ||||
|         self._parent.current_exec_line = getattr(self._parent, 'current_exec_line', None) | ||||
|         self._parent.current_add_game_dialog = getattr(self._parent, 'current_add_game_dialog', None) | ||||
|  | ||||
|         self.axis_deadzone = axis_deadzone | ||||
|         self.initial_axis_move_delay = initial_axis_move_delay | ||||
| @@ -115,6 +117,225 @@ class InputManager(QObject): | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in handle_fullscreen_slot: {e}", exc_info=True) | ||||
|  | ||||
|     @Slot(int) | ||||
|     def handle_button_slot(self, button_code: int) -> None: | ||||
|         try: | ||||
|             # Игнорировать события геймпада, если игра запущена | ||||
|             if getattr(self._parent, '_gameLaunched', False): | ||||
|                 return | ||||
|  | ||||
|             app = QApplication.instance() | ||||
|             if not app: | ||||
|                 return | ||||
|             active = QApplication.activeWindow() | ||||
|             focused = QApplication.focusWidget() | ||||
|  | ||||
|             # Закрытие AddGameDialog на кнопку B | ||||
|             if button_code in BUTTONS['back'] and isinstance(active, QDialog): | ||||
|                 active.reject()  # Закрываем диалог | ||||
|                 return | ||||
|  | ||||
|             # FullscreenDialog | ||||
|             if isinstance(active, FullscreenDialog): | ||||
|                 if button_code in BUTTONS['prev_tab']: | ||||
|                     active.show_prev() | ||||
|                 elif button_code in BUTTONS['next_tab']: | ||||
|                     active.show_next() | ||||
|                 elif button_code in BUTTONS['back']: | ||||
|                     active.close() | ||||
|                 return | ||||
|  | ||||
|             # Context menu for GameCard | ||||
|             if isinstance(focused, GameCard): | ||||
|                 if button_code in BUTTONS['context_menu']: | ||||
|                     pos = QPoint(focused.width() // 2, focused.height() // 2) | ||||
|                     focused._show_context_menu(pos) | ||||
|                     return | ||||
|  | ||||
|             # Game launch on detail page | ||||
|             if (button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']) and self._parent.currentDetailPage is not None and self._parent.current_add_game_dialog is None: | ||||
|                 if self._parent.current_exec_line: | ||||
|                     self._parent.toggleGame(self._parent.current_exec_line, None) | ||||
|                     return | ||||
|  | ||||
|             # Standard navigation | ||||
|             if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']: | ||||
|                 self._parent.activateFocusedWidget() | ||||
|             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']: | ||||
|                 self._parent.openAddGameDialog() | ||||
|             elif button_code in BUTTONS['prev_tab']: | ||||
|                 idx = (self._parent.stackedWidget.currentIndex() - 1) % len(self._parent.tabButtons) | ||||
|                 self._parent.switchTab(idx) | ||||
|                 self._parent.tabButtons[idx].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|             elif button_code in BUTTONS['next_tab']: | ||||
|                 idx = (self._parent.stackedWidget.currentIndex() + 1) % len(self._parent.tabButtons) | ||||
|                 self._parent.switchTab(idx) | ||||
|                 self._parent.tabButtons[idx].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|         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: | ||||
|             # Игнорировать события геймпада, если игра запущена | ||||
|             if getattr(self._parent, '_gameLaunched', False): | ||||
|                 return | ||||
|  | ||||
|             app = QApplication.instance() | ||||
|             if not app: | ||||
|                 return | ||||
|             active = QApplication.activeWindow() | ||||
|  | ||||
|             # Fullscreen horizontal navigation | ||||
|             if isinstance(active, FullscreenDialog) and code == ecodes.ABS_HAT0X: | ||||
|                 if value < 0: | ||||
|                     active.show_prev() | ||||
|                 elif value > 0: | ||||
|                     active.show_next() | ||||
|                 return | ||||
|  | ||||
|             # Handle repeated D-pad movement | ||||
|             if value != 0: | ||||
|                 if not self.axis_moving: | ||||
|                     self.axis_moving = True | ||||
|                 elif (current_time - self.last_move_time) < self.current_axis_delay: | ||||
|                     return | ||||
|                 self.last_move_time = current_time | ||||
|                 self.current_axis_delay = self.repeat_axis_move_delay | ||||
|             else: | ||||
|                 self.axis_moving = False | ||||
|                 self.current_axis_delay = self.initial_axis_move_delay | ||||
|                 return | ||||
|  | ||||
|             # Library tab navigation (index 0) | ||||
|             if self._parent.stackedWidget.currentIndex() == 0 and code in (ecodes.ABS_HAT0X, ecodes.ABS_HAT0Y): | ||||
|                 focused = QApplication.focusWidget() | ||||
|                 game_cards = self._parent.gamesListWidget.findChildren(GameCard) | ||||
|                 if not game_cards: | ||||
|                     return | ||||
|  | ||||
|                 scroll_area = self._parent.gamesListWidget.parentWidget() | ||||
|                 while scroll_area and not isinstance(scroll_area, QScrollArea): | ||||
|                     scroll_area = scroll_area.parentWidget() | ||||
|  | ||||
|                 # If no focused widget or not a GameCard, focus the first card | ||||
|                 if not isinstance(focused, GameCard) or focused not in game_cards: | ||||
|                     game_cards[0].setFocus() | ||||
|                     if scroll_area: | ||||
|                         scroll_area.ensureWidgetVisible(game_cards[0], 50, 50) | ||||
|                     return | ||||
|  | ||||
|                 # Group cards by rows based on y-coordinate | ||||
|                 rows = {} | ||||
|                 for card in game_cards: | ||||
|                     y = card.pos().y() | ||||
|                     if y not in rows: | ||||
|                         rows[y] = [] | ||||
|                     rows[y].append(card) | ||||
|                 # Sort cards in each row by x-coordinate | ||||
|                 for y in rows: | ||||
|                     rows[y].sort(key=lambda c: c.pos().x()) | ||||
|                 # Sort rows by y-coordinate | ||||
|                 sorted_rows = sorted(rows.items(), key=lambda x: x[0]) | ||||
|  | ||||
|                 # Find current row and column | ||||
|                 current_y = focused.pos().y() | ||||
|                 current_row_idx = next(i for i, (y, _) in enumerate(sorted_rows) if y == current_y) | ||||
|                 current_row = sorted_rows[current_row_idx][1] | ||||
|                 current_col_idx = current_row.index(focused) | ||||
|  | ||||
|                 if code == ecodes.ABS_HAT0X and value != 0:  # Left/Right | ||||
|                     if value < 0:  # Left | ||||
|                         next_col_idx = current_col_idx - 1 | ||||
|                         if next_col_idx >= 0: | ||||
|                             next_card = current_row[next_col_idx] | ||||
|                             next_card.setFocus() | ||||
|                             if scroll_area: | ||||
|                                 scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         else: | ||||
|                             # Move to the last card of the previous row if available | ||||
|                             if current_row_idx > 0: | ||||
|                                 prev_row = sorted_rows[current_row_idx - 1][1] | ||||
|                                 next_card = prev_row[-1] if prev_row else None | ||||
|                                 if next_card: | ||||
|                                     next_card.setFocus() | ||||
|                                     if scroll_area: | ||||
|                                         scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                     elif value > 0:  # Right | ||||
|                         next_col_idx = current_col_idx + 1 | ||||
|                         if next_col_idx < len(current_row): | ||||
|                             next_card = current_row[next_col_idx] | ||||
|                             next_card.setFocus() | ||||
|                             if scroll_area: | ||||
|                                 scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         else: | ||||
|                             # Move to the first card of the next row if available | ||||
|                             if current_row_idx < len(sorted_rows) - 1: | ||||
|                                 next_row = sorted_rows[current_row_idx + 1][1] | ||||
|                                 next_card = next_row[0] if next_row else None | ||||
|                                 if next_card: | ||||
|                                     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 | ||||
|                         if next_row_idx < len(sorted_rows): | ||||
|                             next_row = sorted_rows[next_row_idx][1] | ||||
|                             # Find card in same column or closest | ||||
|                             target_x = focused.pos().x() | ||||
|                             next_card = min( | ||||
|                                 next_row, | ||||
|                                 key=lambda c: abs(c.pos().x() - target_x), | ||||
|                                 default=None | ||||
|                             ) | ||||
|                             if next_card: | ||||
|                                 next_card.setFocus() | ||||
|                                 if scroll_area: | ||||
|                                     scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                     elif value < 0:  # Up | ||||
|                         next_row_idx = current_row_idx - 1 | ||||
|                         if next_row_idx >= 0: | ||||
|                             next_row = sorted_rows[next_row_idx][1] | ||||
|                             # Find card in same column or closest | ||||
|                             target_x = focused.pos().x() | ||||
|                             next_card = min( | ||||
|                                 next_row, | ||||
|                                 key=lambda c: abs(c.pos().x() - target_x), | ||||
|                                 default=None | ||||
|                             ) | ||||
|                             if next_card: | ||||
|                                 next_card.setFocus() | ||||
|                                 if scroll_area: | ||||
|                                     scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         elif current_row_idx == 0: | ||||
|                             self._parent.tabButtons[0].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|  | ||||
|             # Vertical navigation in other tabs | ||||
|             elif code == ecodes.ABS_HAT0Y and value != 0: | ||||
|                 focused = QApplication.focusWidget() | ||||
|                 page = self._parent.stackedWidget.currentWidget() | ||||
|                 if value > 0:  # Down | ||||
|                     if isinstance(focused, NavLabel): | ||||
|                         focusables = page.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively) | ||||
|                         focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus] | ||||
|                         if focusables: | ||||
|                             focusables[0].setFocus() | ||||
|                             return | ||||
|                     elif focused: | ||||
|                         focused.focusNextChild() | ||||
|                         return | ||||
|                 elif value < 0 and focused:  # Up | ||||
|                     focused.focusPreviousChild() | ||||
|                     return | ||||
|  | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in handle_dpad_slot: {e}", exc_info=True) | ||||
|  | ||||
|     def eventFilter(self, obj: QObject, event: QEvent) -> bool: | ||||
|         app = QApplication.instance() | ||||
|         if not app: | ||||
| @@ -134,6 +355,11 @@ class InputManager(QObject): | ||||
|             app.quit() | ||||
|             return True | ||||
|  | ||||
|         # Закрытие AddGameDialog на Esc | ||||
|         if key == Qt.Key.Key_Escape and isinstance(popup, QDialog): | ||||
|             popup.reject()  # Закрываем диалог | ||||
|             return True | ||||
|  | ||||
|         # Skip navigation keys if a popup is open | ||||
|         if popup: | ||||
|             return False | ||||
| @@ -164,60 +390,126 @@ class InputManager(QObject): | ||||
|                 focused._show_context_menu(pos) | ||||
|                 return True | ||||
|  | ||||
|         # Navigation in Library tab | ||||
|         # Tab switching with Left/Right keys (non-GameCard focus or no focus) | ||||
|         idx = self._parent.stackedWidget.currentIndex() | ||||
|         total = len(self._parent.tabButtons) | ||||
|         if key == Qt.Key.Key_Left and (not isinstance(focused, GameCard) or focused is None): | ||||
|             new = (idx - 1) % total | ||||
|             self._parent.switchTab(new) | ||||
|             self._parent.tabButtons[new].setFocus() | ||||
|             return True | ||||
|         if key == Qt.Key.Key_Right and (not isinstance(focused, GameCard) or focused is None): | ||||
|             new = (idx + 1) % total | ||||
|             self._parent.switchTab(new) | ||||
|             self._parent.tabButtons[new].setFocus() | ||||
|             return True | ||||
|  | ||||
|         # Library tab navigation | ||||
|         if self._parent.stackedWidget.currentIndex() == 0: | ||||
|             game_cards = self._parent.gamesListWidget.findChildren(GameCard) | ||||
|             scroll_area = self._parent.gamesListWidget.parentWidget() | ||||
|             while scroll_area and not isinstance(scroll_area, QScrollArea): | ||||
|                 scroll_area = scroll_area.parentWidget() | ||||
|  | ||||
|             if isinstance(focused, GameCard): | ||||
|                 current_index = game_cards.index(focused) if focused in game_cards else -1 | ||||
|                 if key == Qt.Key.Key_Down: | ||||
|                     if current_index >= 0 and current_index + 1 < len(game_cards): | ||||
|                         next_card = game_cards[current_index + 1] | ||||
|             if key in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): | ||||
|                 if not game_cards: | ||||
|                     return True | ||||
|  | ||||
|                 # If no focused widget or not a GameCard, focus the first card | ||||
|                 if not isinstance(focused, GameCard) or focused not in game_cards: | ||||
|                     game_cards[0].setFocus() | ||||
|                     if scroll_area: | ||||
|                         scroll_area.ensureWidgetVisible(game_cards[0], 50, 50) | ||||
|                     return True | ||||
|  | ||||
|                 # Group cards by rows based on y-coordinate | ||||
|                 rows = {} | ||||
|                 for card in game_cards: | ||||
|                     y = card.pos().y() | ||||
|                     if y not in rows: | ||||
|                         rows[y] = [] | ||||
|                     rows[y].append(card) | ||||
|                 # Sort cards in each row by x-coordinate | ||||
|                 for y in rows: | ||||
|                     rows[y].sort(key=lambda c: c.pos().x()) | ||||
|                 # Sort rows by y-coordinate | ||||
|                 sorted_rows = sorted(rows.items(), key=lambda x: x[0]) | ||||
|  | ||||
|                 # Find current row and column | ||||
|                 current_y = focused.pos().y() | ||||
|                 current_row_idx = next(i for i, (y, _) in enumerate(sorted_rows) if y == current_y) | ||||
|                 current_row = sorted_rows[current_row_idx][1] | ||||
|                 current_col_idx = current_row.index(focused) | ||||
|  | ||||
|                 if key == Qt.Key.Key_Right: | ||||
|                     next_col_idx = current_col_idx + 1 | ||||
|                     if next_col_idx < len(current_row): | ||||
|                         next_card = current_row[next_col_idx] | ||||
|                         next_card.setFocus() | ||||
|                         if scroll_area: | ||||
|                             scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         return True | ||||
|                     else: | ||||
|                         # Move to the first card of the next row if available | ||||
|                         if current_row_idx < len(sorted_rows) - 1: | ||||
|                             next_row = sorted_rows[current_row_idx + 1][1] | ||||
|                             next_card = next_row[0] if next_row else None | ||||
|                             if next_card: | ||||
|                                 next_card.setFocus() | ||||
|                                 if scroll_area: | ||||
|                                     scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                             return True | ||||
|                 elif key == Qt.Key.Key_Left: | ||||
|                     next_col_idx = current_col_idx - 1 | ||||
|                     if next_col_idx >= 0: | ||||
|                         next_card = current_row[next_col_idx] | ||||
|                         next_card.setFocus() | ||||
|                         if scroll_area: | ||||
|                             scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         return True | ||||
|                     else: | ||||
|                         # Move to the last card of the previous row if available | ||||
|                         if current_row_idx > 0: | ||||
|                             prev_row = sorted_rows[current_row_idx - 1][1] | ||||
|                             next_card = prev_row[-1] if prev_row else None | ||||
|                             if next_card: | ||||
|                                 next_card.setFocus() | ||||
|                                 if scroll_area: | ||||
|                                     scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                             return True | ||||
|                 elif key == Qt.Key.Key_Down: | ||||
|                     next_row_idx = current_row_idx + 1 | ||||
|                     if next_row_idx < len(sorted_rows): | ||||
|                         next_row = sorted_rows[next_row_idx][1] | ||||
|                         target_x = focused.pos().x() | ||||
|                         next_card = min( | ||||
|                             next_row, | ||||
|                             key=lambda c: abs(c.pos().x() - target_x), | ||||
|                             default=None | ||||
|                         ) | ||||
|                         if next_card: | ||||
|                             next_card.setFocus() | ||||
|                             if scroll_area: | ||||
|                                 scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         return True | ||||
|                 elif key == Qt.Key.Key_Up: | ||||
|                     if current_index > 0: | ||||
|                         prev_card = game_cards[current_index - 1] | ||||
|                         prev_card.setFocus() | ||||
|                         if scroll_area: | ||||
|                             scroll_area.ensureWidgetVisible(prev_card, 50, 50) | ||||
|                     next_row_idx = current_row_idx - 1 | ||||
|                     if next_row_idx >= 0: | ||||
|                         next_row = sorted_rows[next_row_idx][1] | ||||
|                         target_x = focused.pos().x() | ||||
|                         next_card = min( | ||||
|                             next_row, | ||||
|                             key=lambda c: abs(c.pos().x() - target_x), | ||||
|                             default=None | ||||
|                         ) | ||||
|                         if next_card: | ||||
|                             next_card.setFocus() | ||||
|                             if scroll_area: | ||||
|                                 scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         return True | ||||
|                     elif current_index == 0: | ||||
|                     elif current_row_idx == 0: | ||||
|                         self._parent.tabButtons[0].setFocus() | ||||
|                         return True | ||||
|                 elif key == Qt.Key.Key_Left: | ||||
|                     if current_index > 0: | ||||
|                         prev_card = game_cards[current_index - 1] | ||||
|                         prev_card.setFocus() | ||||
|                         if scroll_area: | ||||
|                             scroll_area.ensureWidgetVisible(prev_card, 50, 50) | ||||
|                         return True | ||||
|                 elif key == Qt.Key.Key_Right: | ||||
|                     if current_index >= 0 and current_index + 1 < len(game_cards): | ||||
|                         next_card = game_cards[current_index + 1] | ||||
|                         next_card.setFocus() | ||||
|                         if scroll_area: | ||||
|                             scroll_area.ensureWidgetVisible(next_card, 50, 50) | ||||
|                         return True | ||||
|  | ||||
|         # Tab switching with Left/Right keys | ||||
|         idx = self._parent.stackedWidget.currentIndex() | ||||
|         total = len(self._parent.tabButtons) | ||||
|         if key == Qt.Key.Key_Left and not isinstance(focused, GameCard): | ||||
|             new = (idx - 1) % total | ||||
|             self._parent.switchTab(new) | ||||
|             self._parent.tabButtons[new].setFocus() | ||||
|             return True | ||||
|         if key == Qt.Key.Key_Right and not isinstance(focused, GameCard): | ||||
|             new = (idx + 1) % total | ||||
|             self._parent.switchTab(new) | ||||
|             self._parent.tabButtons[new].setFocus() | ||||
|             return True | ||||
|  | ||||
|         # Navigate down into tab content | ||||
|         if key == Qt.Key.Key_Down: | ||||
| @@ -228,11 +520,6 @@ class InputManager(QObject): | ||||
|                 if focusables: | ||||
|                     focusables[0].setFocus() | ||||
|                     return True | ||||
|             else: | ||||
|                 if focused is not None: | ||||
|                     focused.focusNextChild() | ||||
|                     return True | ||||
|  | ||||
|         # Navigate up through tab content | ||||
|         if key == Qt.Key.Key_Up: | ||||
|             if isinstance(focused, NavLabel): | ||||
| @@ -354,124 +641,6 @@ class InputManager(QObject): | ||||
|                     pass | ||||
|             self.gamepad = None | ||||
|  | ||||
|     @Slot(int) | ||||
|     def handle_button_slot(self, button_code: int) -> None: | ||||
|         try: | ||||
|             app = QApplication.instance() | ||||
|             if not app: | ||||
|                 return | ||||
|             active = QApplication.activeWindow() | ||||
|             focused = QApplication.focusWidget() | ||||
|  | ||||
|             # FullscreenDialog | ||||
|             if isinstance(active, FullscreenDialog): | ||||
|                 if button_code in BUTTONS['prev_tab']: | ||||
|                     active.show_prev() | ||||
|                 elif button_code in BUTTONS['next_tab']: | ||||
|                     active.show_next() | ||||
|                 elif button_code in BUTTONS['back']: | ||||
|                     active.close() | ||||
|                 return | ||||
|  | ||||
|             # Context menu for GameCard | ||||
|             if isinstance(focused, GameCard): | ||||
|                 if button_code in BUTTONS['context_menu']: | ||||
|                     pos = QPoint(focused.width() // 2, focused.height() // 2) | ||||
|                     focused._show_context_menu(pos) | ||||
|                     return | ||||
|  | ||||
|             # Game launch on detail page | ||||
|             if (button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']) and self._parent.currentDetailPage is not None: | ||||
|                 if self._parent.current_exec_line: | ||||
|                     self._parent.toggleGame(self._parent.current_exec_line, None) | ||||
|                     return | ||||
|  | ||||
|             # Standard navigation | ||||
|             if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']: | ||||
|                 self._parent.activateFocusedWidget() | ||||
|             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']: | ||||
|                 self._parent.openAddGameDialog() | ||||
|             elif button_code in BUTTONS['prev_tab']: | ||||
|                 idx = (self._parent.stackedWidget.currentIndex() - 1) % len(self._parent.tabButtons) | ||||
|                 self._parent.switchTab(idx) | ||||
|                 self._parent.tabButtons[idx].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|             elif button_code in BUTTONS['next_tab']: | ||||
|                 idx = (self._parent.stackedWidget.currentIndex() + 1) % len(self._parent.tabButtons) | ||||
|                 self._parent.switchTab(idx) | ||||
|                 self._parent.tabButtons[idx].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|         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: | ||||
|             app = QApplication.instance() | ||||
|             if not app: | ||||
|                 return | ||||
|             active = QApplication.activeWindow() | ||||
|  | ||||
|             # Fullscreen horizontal | ||||
|             if isinstance(active, FullscreenDialog) and code == ecodes.ABS_HAT0X: | ||||
|                 if value < 0: | ||||
|                     active.show_prev() | ||||
|                 elif value > 0: | ||||
|                     active.show_next() | ||||
|                 return | ||||
|  | ||||
|             # Vertical navigation (DPAD up/down) | ||||
|             if code == ecodes.ABS_HAT0Y: | ||||
|                 if value == 0: | ||||
|                     return | ||||
|                 focused = QApplication.focusWidget() | ||||
|                 page = self._parent.stackedWidget.currentWidget() | ||||
|                 if value > 0: | ||||
|                     if isinstance(focused, NavLabel): | ||||
|                         focusables = page.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively) | ||||
|                         focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus] | ||||
|                         if focusables: | ||||
|                             focusables[0].setFocus() | ||||
|                             return | ||||
|                     elif focused: | ||||
|                         focused.focusNextChild() | ||||
|                         return | ||||
|                 elif value < 0 and focused: | ||||
|                     focused.focusPreviousChild() | ||||
|                     return | ||||
|  | ||||
|             # Horizontal wrap navigation repeat logic | ||||
|             if code != ecodes.ABS_HAT0X: | ||||
|                 return | ||||
|             if value == 0: | ||||
|                 self.axis_moving = False | ||||
|                 self.current_axis_delay = self.initial_axis_move_delay | ||||
|                 return | ||||
|             if not self.axis_moving: | ||||
|                 self.trigger_dpad_movement(code, value) | ||||
|                 self.last_move_time = current_time | ||||
|                 self.axis_moving = True | ||||
|             elif current_time - self.last_move_time >= self.current_axis_delay: | ||||
|                 self.trigger_dpad_movement(code, value) | ||||
|                 self.last_move_time = current_time | ||||
|                 self.current_axis_delay = self.repeat_axis_move_delay | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in handle_dpad_slot: {e}", exc_info=True) | ||||
|  | ||||
|     def trigger_dpad_movement(self, code: int, value: int) -> None: | ||||
|         try: | ||||
|             if code != ecodes.ABS_HAT0X: | ||||
|                 return | ||||
|             idx = self._parent.stackedWidget.currentIndex() | ||||
|             if value < 0: | ||||
|                 new = (idx - 1) % len(self._parent.tabButtons) | ||||
|             else: | ||||
|                 new = (idx + 1) % len(self._parent.tabButtons) | ||||
|             self._parent.switchTab(new) | ||||
|             self._parent.tabButtons[new].setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in trigger_dpad_movement: {e}", exc_info=True) | ||||
|  | ||||
|     def cleanup(self) -> None: | ||||
|         try: | ||||
|             self.running = False | ||||
|   | ||||
| @@ -60,6 +60,7 @@ class MainWindow(QMainWindow): | ||||
|         self.games_load_timer.setSingleShot(True) | ||||
|         self.games_load_timer.timeout.connect(self.finalize_game_loading) | ||||
|         self.games_loaded.connect(self.on_games_loaded) | ||||
|         self.current_add_game_dialog = None | ||||
|  | ||||
|         # Добавляем таймер для дебаунсинга сохранения настроек | ||||
|         self.settingsDebounceTimer = QTimer(self) | ||||
| @@ -509,6 +510,7 @@ class MainWindow(QMainWindow): | ||||
|  | ||||
|         self.addGameButton = AutoSizeButton(_("Add Game"), icon=self.theme_manager.get_icon("addgame")) | ||||
|         self.addGameButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE) | ||||
|         self.addGameButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) | ||||
|         self.addGameButton.clicked.connect(self.openAddGameDialog) | ||||
|         layout.addWidget(self.addGameButton, alignment=Qt.AlignmentFlag.AlignRight) | ||||
|  | ||||
| @@ -730,7 +732,14 @@ class MainWindow(QMainWindow): | ||||
|  | ||||
|     def openAddGameDialog(self, exe_path=None): | ||||
|         """Открывает диалоговое окно 'Add Game' с текущей темой.""" | ||||
|         # Проверяем, открыт ли уже диалог | ||||
|         if self.current_add_game_dialog is not None and self.current_add_game_dialog.isVisible(): | ||||
|             self.current_add_game_dialog.activateWindow()  # Активируем существующий диалог | ||||
|             self.current_add_game_dialog.raise_()  # Поднимаем окно | ||||
|             return | ||||
|  | ||||
|         dialog = AddGameDialog(self, self.theme) | ||||
|         self.current_add_game_dialog = dialog  # Сохраняем ссылку на диалог | ||||
|  | ||||
|         # Предзаполняем путь к .exe при drag-and-drop | ||||
|         if exe_path: | ||||
| @@ -738,6 +747,12 @@ class MainWindow(QMainWindow): | ||||
|             dialog.nameEdit.setText(os.path.splitext(os.path.basename(exe_path))[0]) | ||||
|             dialog.updatePreview() | ||||
|  | ||||
|         # Обработчик закрытия диалога | ||||
|         def on_dialog_finished(): | ||||
|             self.current_add_game_dialog = None  # Сбрасываем ссылку при закрытии | ||||
|  | ||||
|         dialog.finished.connect(on_dialog_finished) | ||||
|  | ||||
|         if dialog.exec() == QDialog.DialogCode.Accepted: | ||||
|             name = dialog.nameEdit.text().strip() | ||||
|             exe_path = dialog.exeEdit.text().strip() | ||||
| @@ -774,7 +789,6 @@ class MainWindow(QMainWindow): | ||||
|             self.games = self.loadGames() | ||||
|             self.updateGameGrid() | ||||
|  | ||||
|  | ||||
|     def createAutoInstallTab(self): | ||||
|         """Вкладка 'Auto Install'.""" | ||||
|         self.autoInstallWidget = QWidget() | ||||
|   | ||||
| @@ -200,6 +200,10 @@ ACTION_BUTTON_STYLE = """ | ||||
|     QPushButton:pressed { | ||||
|         background: #282a33; | ||||
|     } | ||||
|     QPushButton:focus { | ||||
|         border: 2px solid #409EFF; | ||||
|         background-color: #404554; | ||||
|     } | ||||
| """ | ||||
|  | ||||
| # ТЕКСТОВЫЕ СТИЛИ: ЗАГОЛОВКИ И ОСНОВНОЙ КОНТЕНТ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user