feat(autoinstall): rework gamepad navigation
Some checks failed
Code check / Check code (push) Has been cancelled
Some checks failed
Code check / Check code (push) Has been cancelled
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -38,6 +38,7 @@ class MainWindowProtocol(Protocol):
|
|||||||
stackedWidget: QStackedWidget
|
stackedWidget: QStackedWidget
|
||||||
tabButtons: dict[int, QWidget]
|
tabButtons: dict[int, QWidget]
|
||||||
gamesListWidget: QWidget
|
gamesListWidget: QWidget
|
||||||
|
autoInstallContainer: QWidget
|
||||||
currentDetailPage: QWidget | None
|
currentDetailPage: QWidget | None
|
||||||
current_exec_line: str | None
|
current_exec_line: str | None
|
||||||
current_add_game_dialog: AddGameDialog | None
|
current_add_game_dialog: AddGameDialog | None
|
||||||
@@ -91,6 +92,7 @@ class InputManager(QObject):
|
|||||||
self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None)
|
self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None)
|
||||||
self._parent.current_exec_line = getattr(self._parent, 'current_exec_line', 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._parent.current_add_game_dialog = getattr(self._parent, 'current_add_game_dialog', None)
|
||||||
|
self._parent.autoInstallContainer = getattr(self._parent, 'autoInstallContainer', None)
|
||||||
self.axis_deadzone = axis_deadzone
|
self.axis_deadzone = axis_deadzone
|
||||||
self.initial_axis_move_delay = initial_axis_move_delay
|
self.initial_axis_move_delay = initial_axis_move_delay
|
||||||
self.repeat_axis_move_delay = repeat_axis_move_delay
|
self.repeat_axis_move_delay = repeat_axis_move_delay
|
||||||
@@ -143,6 +145,132 @@ class InputManager(QObject):
|
|||||||
# Initialize evdev + hotplug
|
# Initialize evdev + hotplug
|
||||||
self.init_gamepad()
|
self.init_gamepad()
|
||||||
|
|
||||||
|
def _navigate_game_cards(self, container, tab_index: int, code: int, value: int) -> None:
|
||||||
|
"""Common navigation logic for game cards in a container."""
|
||||||
|
if container is None:
|
||||||
|
return
|
||||||
|
focused = QApplication.focusWidget()
|
||||||
|
game_cards = container.findChildren(GameCard)
|
||||||
|
if not game_cards:
|
||||||
|
return
|
||||||
|
|
||||||
|
scroll_area = container.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
|
||||||
|
|
||||||
|
cards = container.findChildren(GameCard, options=Qt.FindChildOption.FindChildrenRecursively)
|
||||||
|
if not cards:
|
||||||
|
return
|
||||||
|
# Group cards by rows with tolerance for y-position
|
||||||
|
rows = {}
|
||||||
|
y_tolerance = 10 # Allow slight variations in y-position
|
||||||
|
for card in cards:
|
||||||
|
y = card.pos().y()
|
||||||
|
matched = False
|
||||||
|
for row_y in rows:
|
||||||
|
if abs(y - row_y) <= y_tolerance:
|
||||||
|
rows[row_y].append(card)
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
if not matched:
|
||||||
|
rows[y] = [card]
|
||||||
|
sorted_rows = sorted(rows.items(), key=lambda x: x[0])
|
||||||
|
if not sorted_rows:
|
||||||
|
return
|
||||||
|
current_row_idx = None
|
||||||
|
current_col_idx = None
|
||||||
|
for row_idx, (_y, row_cards) in enumerate(sorted_rows):
|
||||||
|
for idx, card in enumerate(row_cards):
|
||||||
|
if card == focused:
|
||||||
|
current_row_idx = row_idx
|
||||||
|
current_col_idx = idx
|
||||||
|
break
|
||||||
|
if current_row_idx is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Fallback: if focused card not found, select closest row by y-position
|
||||||
|
if current_row_idx is None:
|
||||||
|
if not sorted_rows: # Additional safety check
|
||||||
|
return
|
||||||
|
focused_y = focused.pos().y()
|
||||||
|
current_row_idx = min(range(len(sorted_rows)), key=lambda i: abs(sorted_rows[i][0] - focused_y))
|
||||||
|
if current_row_idx >= len(sorted_rows): # Safety check
|
||||||
|
return
|
||||||
|
current_row = sorted_rows[current_row_idx][1]
|
||||||
|
focused_x = focused.pos().x() + focused.width() / 2
|
||||||
|
current_col_idx = min(range(len(current_row)), key=lambda i: abs((current_row[i].pos().x() + current_row[i].width() / 2) - focused_x), default=0) # type: ignore
|
||||||
|
|
||||||
|
# Add null checks before using current_row_idx and current_col_idx
|
||||||
|
if current_row_idx is None or current_col_idx is None or current_row_idx >= len(sorted_rows):
|
||||||
|
return
|
||||||
|
|
||||||
|
current_row = sorted_rows[current_row_idx][1]
|
||||||
|
if code == ecodes.ABS_HAT0X and value != 0:
|
||||||
|
if value < 0: # Left
|
||||||
|
if current_col_idx > 0:
|
||||||
|
next_card = current_row[current_col_idx - 1]
|
||||||
|
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
else:
|
||||||
|
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(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
elif value > 0: # Right
|
||||||
|
if current_col_idx < len(current_row) - 1:
|
||||||
|
next_card = current_row[current_col_idx + 1]
|
||||||
|
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
else:
|
||||||
|
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(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
elif code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
|
if value > 0: # Down
|
||||||
|
if current_row_idx < len(sorted_rows) - 1:
|
||||||
|
next_row = sorted_rows[current_row_idx + 1][1]
|
||||||
|
current_x = focused.pos().x() + focused.width() / 2
|
||||||
|
next_card = min(
|
||||||
|
next_row,
|
||||||
|
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
||||||
|
default=None
|
||||||
|
)
|
||||||
|
if next_card:
|
||||||
|
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
elif value < 0: # Up
|
||||||
|
if current_row_idx > 0:
|
||||||
|
prev_row = sorted_rows[current_row_idx - 1][1]
|
||||||
|
current_x = focused.pos().x() + focused.width() / 2
|
||||||
|
next_card = min(
|
||||||
|
prev_row,
|
||||||
|
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
||||||
|
default=None
|
||||||
|
)
|
||||||
|
if next_card:
|
||||||
|
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
if scroll_area:
|
||||||
|
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||||
|
elif current_row_idx == 0:
|
||||||
|
self._parent.tabButtons[tab_index].setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
def detect_gamepad_type(self, device: InputDevice) -> GamepadType:
|
def detect_gamepad_type(self, device: InputDevice) -> GamepadType:
|
||||||
"""
|
"""
|
||||||
Определяет тип геймпада по capabilities
|
Определяет тип геймпада по capabilities
|
||||||
@@ -767,32 +895,6 @@ class InputManager(QObject):
|
|||||||
if not app or not active:
|
if not app or not active:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Новый код: обработка перехода на поле поиска
|
|
||||||
if code == ecodes.ABS_HAT0Y and value < 0: # Only D-pad up
|
|
||||||
if isinstance(focused, GameCard):
|
|
||||||
# Get all visible game cards
|
|
||||||
game_cards = self._parent.gamesListWidget.findChildren(GameCard)
|
|
||||||
if not game_cards:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Find the current card's position
|
|
||||||
current_card_pos = focused.pos()
|
|
||||||
current_row_y = current_card_pos.y()
|
|
||||||
|
|
||||||
# Check if this is the first row (no cards above)
|
|
||||||
is_first_row = True
|
|
||||||
for card in game_cards:
|
|
||||||
if card.pos().y() < current_row_y and card.isVisible():
|
|
||||||
is_first_row = False
|
|
||||||
break
|
|
||||||
|
|
||||||
# Only move to search if on first row
|
|
||||||
if is_first_row:
|
|
||||||
search_edit = getattr(self._parent, 'searchEdit', None)
|
|
||||||
if search_edit:
|
|
||||||
search_edit.setFocus()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Handle SystemOverlay, AddGameDialog, or QMessageBox navigation with D-pad
|
# Handle SystemOverlay, AddGameDialog, or QMessageBox navigation with D-pad
|
||||||
if isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0:
|
if isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0:
|
||||||
if isinstance(active, QMessageBox): # Specific handling for QMessageBox
|
if isinstance(active, QMessageBox): # Specific handling for QMessageBox
|
||||||
@@ -908,132 +1010,43 @@ class InputManager(QObject):
|
|||||||
focused.setFocus(Qt.FocusReason.OtherFocusReason)
|
focused.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Library tab navigation (index 0)
|
# Search focus logic for tabs 0 and 1
|
||||||
if self._parent.stackedWidget.currentIndex() == 0 and code in (ecodes.ABS_HAT0X, ecodes.ABS_HAT0Y):
|
if code == ecodes.ABS_HAT0Y and value < 0:
|
||||||
focused = QApplication.focusWidget()
|
focused = QApplication.focusWidget()
|
||||||
game_cards = self._parent.gamesListWidget.findChildren(GameCard)
|
current_index = self._parent.stackedWidget.currentIndex()
|
||||||
if not game_cards:
|
if current_index in (0, 1) and isinstance(focused, GameCard):
|
||||||
return
|
if current_index == 0:
|
||||||
|
container = self._parent.gamesListWidget
|
||||||
|
search_edit = getattr(self._parent, 'searchEdit', None)
|
||||||
|
else:
|
||||||
|
container = self._parent.autoInstallContainer
|
||||||
|
search_edit = getattr(self._parent, 'autoInstallSearchLineEdit', None)
|
||||||
|
if container and search_edit:
|
||||||
|
game_cards = container.findChildren(GameCard)
|
||||||
|
if game_cards:
|
||||||
|
current_card_pos = focused.pos()
|
||||||
|
current_row_y = current_card_pos.y()
|
||||||
|
is_first_row = True
|
||||||
|
for card in game_cards:
|
||||||
|
if card.pos().y() < current_row_y and card.isVisible():
|
||||||
|
is_first_row = False
|
||||||
|
break
|
||||||
|
if is_first_row:
|
||||||
|
search_edit.setFocus()
|
||||||
|
return
|
||||||
|
|
||||||
scroll_area = self._parent.gamesListWidget.parentWidget()
|
# Game cards navigation for tabs 0 and 1
|
||||||
while scroll_area and not isinstance(scroll_area, QScrollArea):
|
if code in (ecodes.ABS_HAT0X, ecodes.ABS_HAT0Y):
|
||||||
scroll_area = scroll_area.parentWidget()
|
current_index = self._parent.stackedWidget.currentIndex()
|
||||||
|
if current_index in (0, 1):
|
||||||
# If no focused widget or not a GameCard, focus the first card
|
container = self._parent.gamesListWidget if current_index == 0 else self._parent.autoInstallContainer
|
||||||
if not isinstance(focused, GameCard) or focused not in game_cards:
|
if container is None:
|
||||||
game_cards[0].setFocus()
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(game_cards[0], 50, 50)
|
|
||||||
return
|
|
||||||
|
|
||||||
cards = self._parent.gamesListWidget.findChildren(GameCard, options=Qt.FindChildOption.FindChildrenRecursively)
|
|
||||||
if not cards:
|
|
||||||
return
|
|
||||||
# Group cards by rows with tolerance for y-position
|
|
||||||
rows = {}
|
|
||||||
y_tolerance = 10 # Allow slight variations in y-position
|
|
||||||
for card in cards:
|
|
||||||
y = card.pos().y()
|
|
||||||
matched = False
|
|
||||||
for row_y in rows:
|
|
||||||
if abs(y - row_y) <= y_tolerance:
|
|
||||||
rows[row_y].append(card)
|
|
||||||
matched = True
|
|
||||||
break
|
|
||||||
if not matched:
|
|
||||||
rows[y] = [card]
|
|
||||||
sorted_rows = sorted(rows.items(), key=lambda x: x[0])
|
|
||||||
if not sorted_rows:
|
|
||||||
return
|
|
||||||
current_row_idx = None
|
|
||||||
current_col_idx = None
|
|
||||||
for row_idx, (_y, row_cards) in enumerate(sorted_rows):
|
|
||||||
for idx, card in enumerate(row_cards):
|
|
||||||
if card == focused:
|
|
||||||
current_row_idx = row_idx
|
|
||||||
current_col_idx = idx
|
|
||||||
break
|
|
||||||
if current_row_idx is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Fallback: if focused card not found, select closest row by y-position
|
|
||||||
if current_row_idx is None:
|
|
||||||
if not sorted_rows: # Additional safety check
|
|
||||||
return
|
return
|
||||||
focused_y = focused.pos().y()
|
self._navigate_game_cards(container, current_index, code, value)
|
||||||
current_row_idx = min(range(len(sorted_rows)), key=lambda i: abs(sorted_rows[i][0] - focused_y))
|
|
||||||
if current_row_idx >= len(sorted_rows): # Safety check
|
|
||||||
return
|
|
||||||
current_row = sorted_rows[current_row_idx][1]
|
|
||||||
focused_x = focused.pos().x() + focused.width() / 2
|
|
||||||
current_col_idx = min(range(len(current_row)), key=lambda i: abs((current_row[i].pos().x() + current_row[i].width() / 2) - focused_x), default=0) # type: ignore
|
|
||||||
|
|
||||||
# Add null checks before using current_row_idx and current_col_idx
|
|
||||||
if current_row_idx is None or current_col_idx is None or current_row_idx >= len(sorted_rows):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
current_row = sorted_rows[current_row_idx][1]
|
|
||||||
if code == ecodes.ABS_HAT0X and value != 0:
|
|
||||||
if value < 0: # Left
|
|
||||||
if current_col_idx > 0:
|
|
||||||
next_card = current_row[current_col_idx - 1]
|
|
||||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
|
||||||
else:
|
|
||||||
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(Qt.FocusReason.OtherFocusReason)
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
|
||||||
elif value > 0: # Right
|
|
||||||
if current_col_idx < len(current_row) - 1:
|
|
||||||
next_card = current_row[current_col_idx + 1]
|
|
||||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
|
||||||
else:
|
|
||||||
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(Qt.FocusReason.OtherFocusReason)
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
|
||||||
elif code == ecodes.ABS_HAT0Y and value != 0:
|
|
||||||
if value > 0: # Down
|
|
||||||
if current_row_idx < len(sorted_rows) - 1:
|
|
||||||
next_row = sorted_rows[current_row_idx + 1][1]
|
|
||||||
current_x = focused.pos().x() + focused.width() / 2
|
|
||||||
next_card = min(
|
|
||||||
next_row,
|
|
||||||
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
|
||||||
default=None
|
|
||||||
)
|
|
||||||
if next_card:
|
|
||||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
if scroll_area:
|
|
||||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
|
||||||
elif value < 0: # Up
|
|
||||||
if current_row_idx > 0:
|
|
||||||
prev_row = sorted_rows[current_row_idx - 1][1]
|
|
||||||
current_x = focused.pos().x() + focused.width() / 2
|
|
||||||
next_card = min(
|
|
||||||
prev_row,
|
|
||||||
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
|
||||||
default=None
|
|
||||||
)
|
|
||||||
if next_card:
|
|
||||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
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
|
# Vertical navigation in other tabs
|
||||||
elif code == ecodes.ABS_HAT0Y and value != 0:
|
if code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
focused = QApplication.focusWidget()
|
focused = QApplication.focusWidget()
|
||||||
page = self._parent.stackedWidget.currentWidget()
|
page = self._parent.stackedWidget.currentWidget()
|
||||||
if value > 0: # Down
|
if value > 0: # Down
|
||||||
|
Reference in New Issue
Block a user