feat(virtual_keyboard): added dpad reapeat movement
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -680,31 +680,41 @@ class InputManager(QObject):
|
|||||||
else:
|
else:
|
||||||
keyboard = getattr(self._parent, 'keyboard', None)
|
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():
|
if keyboard and keyboard.isVisible():
|
||||||
# Обработка горизонтального перемещения (LEFT/RIGHT)
|
# Обработка горизонтального перемещения (LEFT/RIGHT)
|
||||||
if code in (ecodes.ABS_HAT0X, ecodes.ABS_X):
|
if code in (ecodes.ABS_HAT0X, ecodes.ABS_X):
|
||||||
|
normalized_value = 0
|
||||||
if code == ecodes.ABS_X: # Левый стик
|
if code == ecodes.ABS_X: # Левый стик
|
||||||
# Применяем мертвую зону
|
# Применяем мертвую зону
|
||||||
if abs(value) < self.dead_zone:
|
if abs(value) < self.dead_zone:
|
||||||
self.current_dpad_code = None
|
self.current_dpad_code = None
|
||||||
self.current_dpad_value = 0
|
self.current_dpad_value = 0
|
||||||
|
self.axis_moving = False
|
||||||
self.dpad_timer.stop()
|
self.dpad_timer.stop()
|
||||||
return
|
return
|
||||||
|
normalized_value = 1 if value > self.dead_zone else -1
|
||||||
# Нормализуем значение стика (-1, 0, 1)
|
|
||||||
normalized_value = 1 if value > self.dead_zone else (-1 if value < -self.dead_zone else 0)
|
|
||||||
else: # D-pad
|
else: # D-pad
|
||||||
normalized_value = value # D-pad уже дает -1, 0, 1
|
normalized_value = value # D-pad уже дает -1, 0, 1
|
||||||
|
|
||||||
if normalized_value != 0:
|
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: # Вправо
|
if normalized_value > 0: # Вправо
|
||||||
keyboard.move_focus_right()
|
keyboard.move_focus_right()
|
||||||
elif normalized_value < 0: # Влево
|
elif normalized_value < 0: # Влево
|
||||||
@@ -713,28 +723,20 @@ class InputManager(QObject):
|
|||||||
|
|
||||||
# Обработка вертикального перемещения (UP/DOWN)
|
# Обработка вертикального перемещения (UP/DOWN)
|
||||||
elif code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y):
|
elif code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y):
|
||||||
|
normalized_value = 0
|
||||||
if code == ecodes.ABS_Y: # Левый стик
|
if code == ecodes.ABS_Y: # Левый стик
|
||||||
# Применяем мертвую зону
|
# Применяем мертвую зону
|
||||||
if abs(value) < self.dead_zone:
|
if abs(value) < self.dead_zone:
|
||||||
self.current_dpad_code = None
|
self.current_dpad_code = None
|
||||||
self.current_dpad_value = 0
|
self.current_dpad_value = 0
|
||||||
|
self.axis_moving = False
|
||||||
self.dpad_timer.stop()
|
self.dpad_timer.stop()
|
||||||
return
|
return
|
||||||
|
normalized_value = 1 if value > self.dead_zone else -1
|
||||||
# Нормализуем значение стика (-1, 0, 1)
|
|
||||||
normalized_value = 1 if value > self.dead_zone else (-1 if value < -self.dead_zone else 0)
|
|
||||||
else: # D-pad
|
else: # D-pad
|
||||||
normalized_value = value # D-pad уже дает -1, 0, 1
|
normalized_value = value # D-pad уже дает -1, 0, 1
|
||||||
|
|
||||||
if normalized_value != 0:
|
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: # Вниз
|
if normalized_value > 0: # Вниз
|
||||||
keyboard.move_focus_down()
|
keyboard.move_focus_down()
|
||||||
elif normalized_value < 0: # Вверх
|
elif normalized_value < 0: # Вверх
|
||||||
@@ -781,23 +783,6 @@ class InputManager(QObject):
|
|||||||
search_edit.setFocus()
|
search_edit.setFocus()
|
||||||
return
|
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
|
# 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
|
||||||
|
@@ -451,7 +451,7 @@ class VirtualKeyboard(QFrame):
|
|||||||
focused.animateClick()
|
focused.animateClick()
|
||||||
|
|
||||||
def focusNextKey(self, direction: str):
|
def focusNextKey(self, direction: str):
|
||||||
"""Перемещает фокус на следующую кнопку в указанном направлении"""
|
"""Перемещает фокус на следующую кнопку в указанном направлении с обертыванием"""
|
||||||
current = self.focusWidget()
|
current = self.focusWidget()
|
||||||
if not current:
|
if not current:
|
||||||
first_button = self.findFirstFocusableButton()
|
first_button = self.findFirstFocusableButton()
|
||||||
@@ -466,48 +466,120 @@ class VirtualKeyboard(QFrame):
|
|||||||
position = cast(tuple[int, int, int, int], self.keyboard_layout.getItemPosition(current_idx))
|
position = cast(tuple[int, int, int, int], self.keyboard_layout.getItemPosition(current_idx))
|
||||||
current_row, current_col, row_span, col_span = position
|
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":
|
if direction == "right":
|
||||||
next_col = current_col + col_span
|
# Сначала ищем в той же строке вправо
|
||||||
next_row = current_row
|
search_row = current_row
|
||||||
max_attempts = self.keyboard_layout.columnCount() - next_col
|
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":
|
elif direction == "left":
|
||||||
next_col = current_col - 1
|
# Сначала ищем в той же строке влево
|
||||||
next_row = current_row
|
search_row = current_row
|
||||||
max_attempts = next_col + 1
|
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":
|
elif direction == "down":
|
||||||
next_col = current_col
|
# Сначала ищем в том же столбце вниз
|
||||||
next_row = current_row + row_span
|
search_col = current_col
|
||||||
max_attempts = self.keyboard_layout.rowCount() - next_row
|
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":
|
elif direction == "up":
|
||||||
next_col = current_col
|
# Сначала ищем в том же столбце вверх
|
||||||
next_row = current_row - 1
|
search_col = current_col
|
||||||
max_attempts = next_row + 1
|
search_row = current_row - 1
|
||||||
else:
|
while search_row >= 0:
|
||||||
return
|
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
|
if not found:
|
||||||
attempts = 0
|
# Переходим к предыдущему столбцу, начиная с последней строки
|
||||||
|
search_col = (current_col - 1) % num_cols
|
||||||
while attempts < max_attempts:
|
search_row = num_rows - 1
|
||||||
item = self.keyboard_layout.itemAtPosition(next_row, next_col)
|
# Ищем последнюю кнопку в этом столбце
|
||||||
if item and item.widget() and item.widget().isEnabled():
|
while search_row >= 0:
|
||||||
next_button = cast(QPushButton, item.widget())
|
item = self.keyboard_layout.itemAtPosition(search_row, search_col)
|
||||||
break
|
if item and item.widget() and item.widget().isEnabled():
|
||||||
|
next_button = cast(QPushButton, item.widget())
|
||||||
if direction == "right":
|
next_button.setFocus()
|
||||||
next_col += 1
|
found = True
|
||||||
elif direction == "left":
|
break
|
||||||
next_col -= 1
|
search_row -= 1
|
||||||
elif direction == "down":
|
|
||||||
next_row += 1
|
|
||||||
elif direction == "up":
|
|
||||||
next_row -= 1
|
|
||||||
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
if next_button:
|
|
||||||
next_button.setFocus()
|
|
||||||
|
|
||||||
def findFirstFocusableButton(self) -> QPushButton | None:
|
def findFirstFocusableButton(self) -> QPushButton | None:
|
||||||
"""Находит первую фокусируемую кнопку на клавиатуре"""
|
"""Находит первую фокусируемую кнопку на клавиатуре"""
|
||||||
|
Reference in New Issue
Block a user