feat: align keyboard arrow key navigation with D-pad logic
All checks were successful
Code and build check / Check code (push) Successful in 1m26s
Code and build check / Build with uv (push) Successful in 47s

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-06-13 23:33:20 +05:00
parent 2a46cf7a2f
commit 74400d1389

View File

@ -523,240 +523,133 @@ class InputManager(QObject):
if not app:
return super().eventFilter(obj, event)
# Handle only key press events
if not (isinstance(event, QKeyEvent) and event.type() == QEvent.Type.KeyPress):
# Handle key press and release events
if not isinstance(event, QKeyEvent):
return super().eventFilter(obj, event)
key = event.key()
modifiers = event.modifiers()
focused = QApplication.focusWidget()
popup = QApplication.activePopupWidget()
# Open system overlay with Insert
if key == Qt.Key.Key_Insert:
if not popup and not isinstance(QApplication.activeWindow(), QDialog):
self._parent.openSystemOverlay()
return True
# Close application with Ctrl+Q
if key == Qt.Key.Key_Q and modifiers & Qt.KeyboardModifier.ControlModifier:
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
# FullscreenDialog navigation
active_win = QApplication.activeWindow()
if isinstance(active_win, FullscreenDialog):
if key == Qt.Key.Key_Right:
active_win.show_next()
return True
if key == Qt.Key.Key_Left:
active_win.show_prev()
return True
if key in (Qt.Key.Key_Escape, Qt.Key.Key_Return, Qt.Key.Key_Enter, Qt.Key.Key_Backspace):
active_win.close()
return True
# Launch/stop game on detail page
if self._parent.currentDetailPage and key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
if self._parent.current_exec_line:
self._parent.toggleGame(self._parent.current_exec_line, None)
return True
# Context menu for GameCard
if isinstance(focused, GameCard):
if key == Qt.Key.Key_F10 and Qt.KeyboardModifier.ShiftModifier:
pos = QPoint(focused.width() // 2, focused.height() // 2)
focused._show_context_menu(pos)
return True
# Handle Up/Down keys for non-GameCard tabs
if key in (Qt.Key.Key_Up, Qt.Key.Key_Down) and not isinstance(focused, GameCard):
page = self._parent.stackedWidget.currentWidget()
if key == Qt.Key.Key_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 True
elif focused:
focused.focusNextChild()
return True
elif key == Qt.Key.Key_Up and focused:
focused.focusPreviousChild()
return True
# 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 key in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down):
if not game_cards:
# Handle key press events
if event.type() == QEvent.Type.KeyPress:
# Open system overlay with Insert
if key == Qt.Key.Key_Insert:
if not popup and not isinstance(active_win, QDialog):
self._parent.openSystemOverlay()
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)
# Close application with Ctrl+Q
if key == Qt.Key.Key_Q and modifiers & Qt.KeyboardModifier.ControlModifier:
app.quit()
return True
# Close AddGameDialog with Escape
if key == Qt.Key.Key_Escape and isinstance(popup, QDialog):
popup.reject()
return True
# FullscreenDialog navigation
if isinstance(active_win, FullscreenDialog):
if key in (Qt.Key.Key_Escape, Qt.Key.Key_Return, Qt.Key.Key_Enter, Qt.Key.Key_Backspace):
active_win.close()
return True
elif key in (Qt.Key.Key_Left, Qt.Key.Key_Right):
# Navigate screenshots in FullscreenDialog
if key == Qt.Key.Key_Left:
active_win.show_prev()
elif key == Qt.Key.Key_Right:
active_win.show_next()
return True # Consume event to prevent tab switching
# Handle tab switching with Left/Right arrow keys when not in GameCard focus
if key in (Qt.Key.Key_Left, Qt.Key.Key_Right) and (not isinstance(focused, GameCard) or focused is None):
idx = self._parent.stackedWidget.currentIndex()
total = len(self._parent.tabButtons)
if key == Qt.Key.Key_Left:
new_idx = (idx - 1) % total
self._parent.switchTab(new_idx)
self._parent.tabButtons[new_idx].setFocus(Qt.FocusReason.OtherFocusReason)
return True
elif key == Qt.Key.Key_Right:
new_idx = (idx + 1) % total
self._parent.switchTab(new_idx)
self._parent.tabButtons[new_idx].setFocus(Qt.FocusReason.OtherFocusReason)
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
# Map arrow keys to D-pad press events for other contexts
if key in (Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left, Qt.Key.Key_Right):
now = time.time()
dpad_code = None
dpad_value = 0
if key == Qt.Key.Key_Up:
dpad_code = ecodes.ABS_HAT0Y
dpad_value = -1
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:
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_row_idx == 0:
self._parent.tabButtons[0].setFocus()
return True
dpad_code = ecodes.ABS_HAT0Y
dpad_value = 1
elif key == Qt.Key.Key_Left:
dpad_code = ecodes.ABS_HAT0X
dpad_value = -1
elif key == Qt.Key.Key_Right:
dpad_code = ecodes.ABS_HAT0X
dpad_value = 1
# Navigate down into tab content
if key == Qt.Key.Key_Down:
if isinstance(focused, NavLabel):
page = self._parent.stackedWidget.currentWidget()
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()
if dpad_code is not None:
self.dpad_moved.emit(dpad_code, dpad_value, now)
return True
elif focused:
focused.focusNextChild()
# Launch/stop game on detail page
if self._parent.currentDetailPage and key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
if self._parent.current_exec_line:
self._parent.toggleGame(self._parent.current_exec_line, None)
return True
# Context menu for GameCard
if isinstance(focused, GameCard):
if key == Qt.Key.Key_F10 and modifiers & Qt.KeyboardModifier.ShiftModifier:
pos = QPoint(focused.width() // 2, focused.height() // 2)
focused._show_context_menu(pos)
return True
# General actions: Activate, Back, Add
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
self._parent.activateFocusedWidget()
return True
# Navigate up through tab content
if key == Qt.Key.Key_Up:
if isinstance(focused, NavLabel):
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
if isinstance(focused, QLineEdit):
return False
self._parent.goBackDetailPage(self._parent.currentDetailPage)
return True
if focused is not None:
focused.focusPreviousChild()
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
# Toggle fullscreen with F11
if key == Qt.Key.Key_F11:
self.toggle_fullscreen.emit(not self._is_fullscreen)
return True
# General actions: Activate, Back, Add
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
self._parent.activateFocusedWidget()
return True
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
if isinstance(focused, QLineEdit):
return False
self._parent.goBackDetailPage(self._parent.currentDetailPage)
return True
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
# Handle key release events for arrow keys
elif event.type() == QEvent.Type.KeyRelease:
if key in (Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left, Qt.Key.Key_Right):
now = time.time()
dpad_code = None
if key in (Qt.Key.Key_Up, Qt.Key.Key_Down):
dpad_code = ecodes.ABS_HAT0Y
elif key in (Qt.Key.Key_Left, Qt.Key.Key_Right):
dpad_code = ecodes.ABS_HAT0X
# Toggle fullscreen with F11
if key == Qt.Key.Key_F11:
self.toggle_fullscreen.emit(not self._is_fullscreen)
return True
if dpad_code is not None:
# Emit release event with value 0 to stop continuous movement
self.dpad_moved.emit(dpad_code, 0, now)
return True
return super().eventFilter(obj, event)