feat: added virtual keyboard #57
| @@ -680,31 +680,41 @@ class InputManager(QObject): | ||||
|         else: | ||||
|             keyboard = getattr(self._parent, 'keyboard', None) | ||||
|  | ||||
|         # Handle release early | ||||
|         if value == 0: | ||||
|             self.current_dpad_code = None | ||||
|             self.current_dpad_value = 0 | ||||
|             self.axis_moving = False | ||||
|             self.current_axis_delay = self.initial_axis_move_delay | ||||
|             self.dpad_timer.stop() | ||||
|             return | ||||
|  | ||||
|         # Update D-pad state for continuous movement | ||||
|         self.current_dpad_code = code | ||||
|         self.current_dpad_value = value | ||||
|         if not self.axis_moving: | ||||
|             self.axis_moving = True | ||||
|             self.last_move_time = current_time | ||||
|             self.current_axis_delay = self.initial_axis_move_delay | ||||
|             self.dpad_timer.start(int(self.repeat_axis_move_delay * 1000)) | ||||
|  | ||||
|         if keyboard and keyboard.isVisible(): | ||||
|             # Обработка горизонтального перемещения (LEFT/RIGHT) | ||||
|             if code in (ecodes.ABS_HAT0X, ecodes.ABS_X): | ||||
|                 normalized_value = 0 | ||||
|                 if code == ecodes.ABS_X:  # Левый стик | ||||
|                     # Применяем мертвую зону | ||||
|                     if abs(value) < self.dead_zone: | ||||
|                         self.current_dpad_code = None | ||||
|                         self.current_dpad_value = 0 | ||||
|                         self.axis_moving = False | ||||
|                         self.dpad_timer.stop() | ||||
|                         return | ||||
|  | ||||
|                     # Нормализуем значение стика (-1, 0, 1) | ||||
|                     normalized_value = 1 if value > self.dead_zone else (-1 if value < -self.dead_zone else 0) | ||||
|                     normalized_value = 1 if value > self.dead_zone else -1 | ||||
|                 else:  # D-pad | ||||
|                     normalized_value = value  # D-pad уже дает -1, 0, 1 | ||||
|  | ||||
|                 if normalized_value != 0: | ||||
|                     # Ограничиваем частоту перемещений | ||||
|                     now = time.time() | ||||
|                     if now - self.last_move_time < self.current_axis_delay: | ||||
|                         return | ||||
|  | ||||
|                     self.last_move_time = now | ||||
|                     self.current_axis_delay = self.repeat_axis_move_delay  # Уменьшаем задержку после первого перемещения | ||||
|  | ||||
|                     if normalized_value > 0:  # Вправо | ||||
|                         keyboard.move_focus_right() | ||||
|                     elif normalized_value < 0:  # Влево | ||||
| @@ -713,28 +723,20 @@ class InputManager(QObject): | ||||
|  | ||||
|             # Обработка вертикального перемещения (UP/DOWN) | ||||
|             elif code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y): | ||||
|                 normalized_value = 0 | ||||
|                 if code == ecodes.ABS_Y:  # Левый стик | ||||
|                     # Применяем мертвую зону | ||||
|                     if abs(value) < self.dead_zone: | ||||
|                         self.current_dpad_code = None | ||||
|                         self.current_dpad_value = 0 | ||||
|                         self.axis_moving = False | ||||
|                         self.dpad_timer.stop() | ||||
|                         return | ||||
|  | ||||
|                     # Нормализуем значение стика (-1, 0, 1) | ||||
|                     normalized_value = 1 if value > self.dead_zone else (-1 if value < -self.dead_zone else 0) | ||||
|                     normalized_value = 1 if value > self.dead_zone else -1 | ||||
|                 else:  # D-pad | ||||
|                     normalized_value = value  # D-pad уже дает -1, 0, 1 | ||||
|  | ||||
|                 if normalized_value != 0: | ||||
|                     # Ограничиваем частоту перемещений | ||||
|                     now = time.time() | ||||
|                     if now - self.last_move_time < self.current_axis_delay: | ||||
|                         return | ||||
|  | ||||
|                     self.last_move_time = now | ||||
|                     self.current_axis_delay = self.repeat_axis_move_delay  # Уменьшаем задержку после первого перемещения | ||||
|  | ||||
|                     if normalized_value > 0:  # Вниз | ||||
|                         keyboard.move_focus_down() | ||||
|                     elif normalized_value < 0:  # Вверх | ||||
| @@ -781,23 +783,6 @@ class InputManager(QObject): | ||||
|                             search_edit.setFocus() | ||||
|                             return | ||||
|  | ||||
|             # Update D-pad state | ||||
|             if value != 0: | ||||
|                 self.current_dpad_code = code | ||||
|                 self.current_dpad_value = value | ||||
|                 if not self.axis_moving: | ||||
|                     self.axis_moving = True | ||||
|                     self.last_move_time = current_time | ||||
|                     self.current_axis_delay = self.initial_axis_move_delay | ||||
|                     self.dpad_timer.start(int(self.repeat_axis_move_delay * 1000))  # Start timer (in milliseconds) | ||||
|             else: | ||||
|                 self.current_dpad_code = None | ||||
|                 self.current_dpad_value = 0 | ||||
|                 self.axis_moving = False | ||||
|                 self.current_axis_delay = self.initial_axis_move_delay | ||||
|                 self.dpad_timer.stop()  # Stop timer when D-pad is released | ||||
|                 return | ||||
|  | ||||
|             # Handle SystemOverlay, AddGameDialog, or QMessageBox navigation with D-pad | ||||
|             if isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0: | ||||
|                 if isinstance(active, QMessageBox):  # Specific handling for QMessageBox | ||||
|   | ||||
| @@ -451,7 +451,7 @@ class VirtualKeyboard(QFrame): | ||||
|             focused.animateClick() | ||||
|  | ||||
|     def focusNextKey(self, direction: str): | ||||
|         """Перемещает фокус на следующую кнопку в указанном направлении""" | ||||
|         """Перемещает фокус на следующую кнопку в указанном направлении с обертыванием""" | ||||
|         current = self.focusWidget() | ||||
|         if not current: | ||||
|             first_button = self.findFirstFocusableButton() | ||||
| @@ -466,48 +466,120 @@ class VirtualKeyboard(QFrame): | ||||
|         position = cast(tuple[int, int, int, int], self.keyboard_layout.getItemPosition(current_idx)) | ||||
|         current_row, current_col, row_span, col_span = position | ||||
|  | ||||
|         # Поиск следующей кнопки | ||||
|         num_rows = self.keyboard_layout.rowCount() | ||||
|         num_cols = self.keyboard_layout.columnCount() | ||||
|  | ||||
|         found = False | ||||
|  | ||||
|         if direction == "right": | ||||
|             next_col = current_col + col_span | ||||
|             next_row = current_row | ||||
|             max_attempts = self.keyboard_layout.columnCount() - next_col | ||||
|             # Сначала ищем в той же строке вправо | ||||
|             search_row = current_row | ||||
|             search_col = current_col + col_span | ||||
|             while search_col < num_cols: | ||||
|                 item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                 if item and item.widget() and item.widget().isEnabled(): | ||||
|                     next_button = cast(QPushButton, item.widget()) | ||||
|                     next_button.setFocus() | ||||
|                     found = True | ||||
|                     break | ||||
|                 search_col += 1 | ||||
|  | ||||
|             if not found: | ||||
|                 # Переходим к следующей строке, начиная с col 0 | ||||
|                 search_row = (current_row + 1) % num_rows | ||||
|                 search_col = 0 | ||||
|                 # Ищем первую кнопку в этой строке | ||||
|                 while search_col < num_cols: | ||||
|                     item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                     if item and item.widget() and item.widget().isEnabled(): | ||||
|                         next_button = cast(QPushButton, item.widget()) | ||||
|                         next_button.setFocus() | ||||
|                         found = True | ||||
|                         break | ||||
|                     search_col += 1 | ||||
|                 # Если не нашли в этой строке, продолжаем к следующей, но для простоты останавливаемся | ||||
|                 # (можно добавить полный цикл, но предполагаем, что строки не пустые) | ||||
|  | ||||
|         elif direction == "left": | ||||
|             next_col = current_col - 1 | ||||
|             next_row = current_row | ||||
|             max_attempts = next_col + 1 | ||||
|             # Сначала ищем в той же строке влево | ||||
|             search_row = current_row | ||||
|             search_col = current_col - 1 | ||||
|             while search_col >= 0: | ||||
|                 item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                 if item and item.widget() and item.widget().isEnabled(): | ||||
|                     next_button = cast(QPushButton, item.widget()) | ||||
|                     next_button.setFocus() | ||||
|                     found = True | ||||
|                     break | ||||
|                 search_col -= 1 | ||||
|  | ||||
|             if not found: | ||||
|                 # Переходим к предыдущей строке, начиная с последнего столбца | ||||
|                 search_row = (current_row - 1) % num_rows | ||||
|                 search_col = num_cols - 1 | ||||
|                 # Ищем последнюю кнопку в этой строке | ||||
|                 while search_col >= 0: | ||||
|                     item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                     if item and item.widget() and item.widget().isEnabled(): | ||||
|                         next_button = cast(QPushButton, item.widget()) | ||||
|                         next_button.setFocus() | ||||
|                         found = True | ||||
|                         break | ||||
|                     search_col -= 1 | ||||
|  | ||||
|         elif direction == "down": | ||||
|             next_col = current_col | ||||
|             next_row = current_row + row_span | ||||
|             max_attempts = self.keyboard_layout.rowCount() - next_row | ||||
|             # Сначала ищем в том же столбце вниз | ||||
|             search_col = current_col | ||||
|             search_row = current_row + row_span | ||||
|             while search_row < num_rows: | ||||
|                 item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                 if item and item.widget() and item.widget().isEnabled(): | ||||
|                     next_button = cast(QPushButton, item.widget()) | ||||
|                     next_button.setFocus() | ||||
|                     found = True | ||||
|                     break | ||||
|                 search_row += 1 | ||||
|  | ||||
|             if not found: | ||||
|                 # Переходим к следующему столбцу, начиная с row 0 | ||||
|                 search_col = (current_col + col_span) % num_cols | ||||
|                 search_row = 0 | ||||
|                 # Ищем первую кнопку в этом столбце | ||||
|                 while search_row < num_rows: | ||||
|                     item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                     if item and item.widget() and item.widget().isEnabled(): | ||||
|                         next_button = cast(QPushButton, item.widget()) | ||||
|                         next_button.setFocus() | ||||
|                         found = True | ||||
|                         break | ||||
|                     search_row += 1 | ||||
|  | ||||
|         elif direction == "up": | ||||
|             next_col = current_col | ||||
|             next_row = current_row - 1 | ||||
|             max_attempts = next_row + 1 | ||||
|         else: | ||||
|             return | ||||
|             # Сначала ищем в том же столбце вверх | ||||
|             search_col = current_col | ||||
|             search_row = current_row - 1 | ||||
|             while search_row >= 0: | ||||
|                 item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                 if item and item.widget() and item.widget().isEnabled(): | ||||
|                     next_button = cast(QPushButton, item.widget()) | ||||
|                     next_button.setFocus() | ||||
|                     found = True | ||||
|                     break | ||||
|                 search_row -= 1 | ||||
|  | ||||
|         next_button = None | ||||
|         attempts = 0 | ||||
|  | ||||
|         while attempts < max_attempts: | ||||
|             item = self.keyboard_layout.itemAtPosition(next_row, next_col) | ||||
|             if item and item.widget() and item.widget().isEnabled(): | ||||
|                 next_button = cast(QPushButton, item.widget()) | ||||
|                 break | ||||
|  | ||||
|             if direction == "right": | ||||
|                 next_col += 1 | ||||
|             elif direction == "left": | ||||
|                 next_col -= 1 | ||||
|             elif direction == "down": | ||||
|                 next_row += 1 | ||||
|             elif direction == "up": | ||||
|                 next_row -= 1 | ||||
|  | ||||
|             attempts += 1 | ||||
|  | ||||
|         if next_button: | ||||
|             next_button.setFocus() | ||||
|             if not found: | ||||
|                 # Переходим к предыдущему столбцу, начиная с последней строки | ||||
|                 search_col = (current_col - 1) % num_cols | ||||
|                 search_row = num_rows - 1 | ||||
|                 # Ищем последнюю кнопку в этом столбце | ||||
|                 while search_row >= 0: | ||||
|                     item = self.keyboard_layout.itemAtPosition(search_row, search_col) | ||||
|                     if item and item.widget() and item.widget().isEnabled(): | ||||
|                         next_button = cast(QPushButton, item.widget()) | ||||
|                         next_button.setFocus() | ||||
|                         found = True | ||||
|                         break | ||||
|                     search_row -= 1 | ||||
|  | ||||
|     def findFirstFocusableButton(self) -> QPushButton | None: | ||||
|         """Находит первую фокусируемую кнопку на клавиатуре""" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user