Compare commits
8 Commits
3f0d259bf0
...
6853cfd5f0
Author | SHA1 | Date | |
---|---|---|---|
6853cfd5f0
|
|||
6ab3c4d232
|
|||
34ce0a74b0
|
|||
88e9d1d7c5
|
|||
4af4d1f0b0
|
|||
69d2960312
|
|||
afad92d967
|
|||
3f0e7487df
|
@@ -9,6 +9,7 @@
|
|||||||
- Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD
|
- Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD
|
||||||
- Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов)
|
- Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов)
|
||||||
- Автодополнение bash для комманды portprotonqt
|
- Автодополнение bash для комманды portprotonqt
|
||||||
|
- Поддержка геймпадов в диалоге выбора игры
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Удалены сборки для Fedora 40
|
- Удалены сборки для Fedora 40
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
- @Dervart
|
- @Dervart
|
||||||
|
- @Vector_null
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -20,9 +20,9 @@ Current translation status:
|
|||||||
|
|
||||||
| Locale | Progress | Translated |
|
| Locale | Progress | Translated |
|
||||||
| :----- | -------: | ---------: |
|
| :----- | -------: | ---------: |
|
||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 191 |
|
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 190 |
|
||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 191 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 190 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 191 of 191 |
|
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 190 of 190 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
| Локаль | Прогресс | Переведено |
|
| Локаль | Прогресс | Переведено |
|
||||||
| :----- | -------: | ---------: |
|
| :----- | -------: | ---------: |
|
||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 191 |
|
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 190 |
|
||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 191 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 190 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 191 из 191 |
|
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 190 из 190 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -1,19 +1,20 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from typing import cast
|
||||||
from PySide6.QtGui import QPixmap
|
from PySide6.QtGui import QPixmap
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog, QLineEdit, QFormLayout, QPushButton,
|
QDialog, QLineEdit, QFormLayout, QPushButton,
|
||||||
QHBoxLayout, QDialogButtonBox, QFileDialog, QLabel
|
QHBoxLayout, QDialogButtonBox, QLabel, QVBoxLayout, QListWidget
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, QObject, Signal
|
||||||
from icoextract import IconExtractor, IconExtractorError
|
from icoextract import IconExtractor, IconExtractorError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from portprotonqt.main_window import MainWindow # Import MainWindow for type casting
|
||||||
from portprotonqt.config_utils import get_portproton_location
|
from portprotonqt.config_utils import get_portproton_location
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
import portprotonqt.themes.standart.styles as default_styles
|
||||||
|
from portprotonqt.themes.standart.styles import FileExplorerStyles
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -81,6 +82,152 @@ def generate_thumbnail(inputfile, outfile, size=128, force_resize=True):
|
|||||||
logger.error(f"Ошибка при сохранении миниатюры: {e}")
|
logger.error(f"Ошибка при сохранении миниатюры: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
class FileSelectedSignal(QObject):
|
||||||
|
file_selected = Signal(str) # Сигнал с путем к выбранному файлу
|
||||||
|
|
||||||
|
class FileExplorer(QDialog):
|
||||||
|
def __init__(self, parent=None, file_filter=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.file_signal = FileSelectedSignal()
|
||||||
|
self.file_filter = file_filter # Store the file filter
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
# Настройки окна
|
||||||
|
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
|
||||||
|
self.setStyleSheet(FileExplorerStyles.WINDOW_STYLE)
|
||||||
|
|
||||||
|
# Find InputManager from parent
|
||||||
|
self.input_manager = None
|
||||||
|
parent = self.parent()
|
||||||
|
while parent:
|
||||||
|
if hasattr(parent, 'input_manager'):
|
||||||
|
self.input_manager = cast(MainWindow, parent).input_manager
|
||||||
|
break
|
||||||
|
parent = parent.parent()
|
||||||
|
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.enable_file_explorer_mode(self)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""Настройка интерфейса"""
|
||||||
|
self.setWindowTitle("File Explorer")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
self.main_layout = QVBoxLayout()
|
||||||
|
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
||||||
|
self.main_layout.setSpacing(10)
|
||||||
|
self.setLayout(self.main_layout)
|
||||||
|
|
||||||
|
self.path_label = QLabel()
|
||||||
|
self.path_label.setStyleSheet(FileExplorerStyles.PATH_LABEL_STYLE)
|
||||||
|
self.main_layout.addWidget(self.path_label)
|
||||||
|
|
||||||
|
# Список файлов
|
||||||
|
self.file_list = QListWidget()
|
||||||
|
self.file_list.setStyleSheet(FileExplorerStyles.LIST_STYLE)
|
||||||
|
self.file_list.itemClicked.connect(self.handle_item_click)
|
||||||
|
self.main_layout.addWidget(self.file_list)
|
||||||
|
|
||||||
|
# Кнопки
|
||||||
|
self.button_layout = QHBoxLayout()
|
||||||
|
self.button_layout.setSpacing(10)
|
||||||
|
self.select_button = QPushButton(_("Select"))
|
||||||
|
self.cancel_button = QPushButton(_("Cancel"))
|
||||||
|
self.select_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
|
||||||
|
self.cancel_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
|
||||||
|
self.button_layout.addWidget(self.select_button)
|
||||||
|
self.button_layout.addWidget(self.cancel_button)
|
||||||
|
self.main_layout.addLayout(self.button_layout)
|
||||||
|
|
||||||
|
self.select_button.clicked.connect(self.select_item)
|
||||||
|
self.cancel_button.clicked.connect(self.reject)
|
||||||
|
|
||||||
|
# Начальная папка
|
||||||
|
self.current_path = os.path.expanduser("~")
|
||||||
|
self.update_file_list()
|
||||||
|
|
||||||
|
def move_selection(self, direction):
|
||||||
|
"""Перемещение выбора по списку"""
|
||||||
|
current_row = self.file_list.currentRow()
|
||||||
|
if direction < 0 and current_row > 0: # Вверх
|
||||||
|
self.file_list.setCurrentRow(current_row - 1)
|
||||||
|
elif direction > 0 and current_row < self.file_list.count() - 1: # Вниз
|
||||||
|
self.file_list.setCurrentRow(current_row + 1)
|
||||||
|
self.file_list.scrollToItem(self.file_list.currentItem())
|
||||||
|
|
||||||
|
def handle_item_click(self, item):
|
||||||
|
"""Обработка клика мышью"""
|
||||||
|
self.file_list.setCurrentItem(item)
|
||||||
|
self.select_item()
|
||||||
|
|
||||||
|
def select_item(self):
|
||||||
|
"""Обработка выбора файла/папки"""
|
||||||
|
if self.file_list.count() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected = self.file_list.currentItem().text()
|
||||||
|
full_path = os.path.join(self.current_path, selected)
|
||||||
|
|
||||||
|
if os.path.isdir(full_path):
|
||||||
|
self.current_path = full_path
|
||||||
|
self.update_file_list()
|
||||||
|
else:
|
||||||
|
self.file_signal.file_selected.emit(full_path)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def update_file_list(self):
|
||||||
|
"""Обновление списка файлов"""
|
||||||
|
self.file_list.clear()
|
||||||
|
try:
|
||||||
|
if self.current_path != "/":
|
||||||
|
self.file_list.addItem("../")
|
||||||
|
|
||||||
|
items = os.listdir(self.current_path)
|
||||||
|
dirs = [d for d in items if os.path.isdir(os.path.join(self.current_path, d))]
|
||||||
|
|
||||||
|
# Apply file filter if provided
|
||||||
|
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
|
||||||
|
if self.file_filter:
|
||||||
|
files = [f for f in files if f.lower().endswith(self.file_filter)]
|
||||||
|
|
||||||
|
for d in sorted(dirs):
|
||||||
|
self.file_list.addItem(f"{d}/")
|
||||||
|
|
||||||
|
for f in sorted(files):
|
||||||
|
self.file_list.addItem(f)
|
||||||
|
|
||||||
|
self.path_label.setText(f"Path: {self.current_path}")
|
||||||
|
self.file_list.setCurrentRow(0)
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
self.path_label.setText(f"Access denied: {self.current_path}")
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
"""Закрытие окна"""
|
||||||
|
try:
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_file_explorer_mode()
|
||||||
|
if self.parent():
|
||||||
|
parent = cast(MainWindow, self.parent())
|
||||||
|
parent.activateWindow()
|
||||||
|
parent.setFocus()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in closeEvent: {e}")
|
||||||
|
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
"""Закрытие диалога"""
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_file_explorer_mode()
|
||||||
|
super().reject()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
"""Принятие диалога"""
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_file_explorer_mode()
|
||||||
|
super().accept()
|
||||||
|
|
||||||
class AddGameDialog(QDialog):
|
class AddGameDialog(QDialog):
|
||||||
def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None):
|
def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None):
|
||||||
@@ -160,28 +307,52 @@ class AddGameDialog(QDialog):
|
|||||||
self.updatePreview()
|
self.updatePreview()
|
||||||
|
|
||||||
def browseExe(self):
|
def browseExe(self):
|
||||||
fileNameAndFilter = QFileDialog.getOpenFileName(
|
"""Открывает файловый менеджер для выбора exe-файла"""
|
||||||
self,
|
try:
|
||||||
_("Select Executable"),
|
file_explorer = FileExplorer(self, file_filter='.exe')
|
||||||
"",
|
file_explorer.file_signal.file_selected.connect(self.onExeSelected)
|
||||||
"Windows Executables (*.exe)"
|
|
||||||
)
|
if self.parent():
|
||||||
fileName = fileNameAndFilter[0]
|
parent = cast(MainWindow, self.parent())
|
||||||
if fileName:
|
center_point = parent.geometry().center()
|
||||||
self.exeEdit.setText(fileName)
|
file_explorer.move(center_point - file_explorer.rect().center())
|
||||||
if not self.edit_mode:
|
|
||||||
self.nameEdit.setText(os.path.splitext(os.path.basename(fileName))[0])
|
file_explorer.show()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in browseExe: {e}")
|
||||||
|
|
||||||
|
def onExeSelected(self, file_path):
|
||||||
|
"""Обработчик выбора файла в FileExplorer"""
|
||||||
|
self.exeEdit.setText(file_path)
|
||||||
|
if not self.edit_mode:
|
||||||
|
# Автоматически заполняем имя игры, если не в режиме редактирования
|
||||||
|
game_name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
|
self.nameEdit.setText(game_name)
|
||||||
|
|
||||||
|
# Обновляем превью
|
||||||
|
self.updatePreview()
|
||||||
|
|
||||||
def browseCover(self):
|
def browseCover(self):
|
||||||
fileNameAndFilter = QFileDialog.getOpenFileName(
|
"""Открывает файловый менеджер для выбора изображения обложки"""
|
||||||
self,
|
try:
|
||||||
_("Select Cover Image"),
|
file_explorer = FileExplorer(self, file_filter=('.png', '.jpg', '.jpeg', '.bmp'))
|
||||||
"",
|
file_explorer.file_signal.file_selected.connect(self.onCoverSelected)
|
||||||
"Images (*.png *.jpg *.jpeg *.bmp)"
|
|
||||||
)
|
if self.parent():
|
||||||
fileName = fileNameAndFilter[0]
|
parent = cast(MainWindow, self.parent())
|
||||||
if fileName:
|
center_point = parent.geometry().center()
|
||||||
self.coverEdit.setText(fileName)
|
file_explorer.move(center_point - file_explorer.rect().center())
|
||||||
|
|
||||||
|
file_explorer.show()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in browseCover: {e}")
|
||||||
|
|
||||||
|
def onCoverSelected(self, file_path):
|
||||||
|
"""Обработчик выбора файла обложки в FileExplorer"""
|
||||||
|
if file_path and os.path.splitext(file_path)[1].lower() in ('.png', '.jpg', '.jpeg', '.bmp'):
|
||||||
|
self.coverEdit.setText(file_path)
|
||||||
|
else:
|
||||||
|
logger.warning(f"Selected file is not a valid image: {file_path}")
|
||||||
|
|
||||||
def updatePreview(self):
|
def updatePreview(self):
|
||||||
"""Update the cover preview image."""
|
"""Update the cover preview image."""
|
||||||
@@ -238,15 +409,15 @@ class AddGameDialog(QDialog):
|
|||||||
comment = _('Launch game "{name}" with PortProton').format(name=name)
|
comment = _('Launch game "{name}" with PortProton').format(name=name)
|
||||||
|
|
||||||
desktop_entry = f"""[Desktop Entry]
|
desktop_entry = f"""[Desktop Entry]
|
||||||
Name={name}
|
Name={name}
|
||||||
Comment={comment}
|
Comment={comment}
|
||||||
Exec={exec_str}
|
Exec={exec_str}
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Game;
|
Categories=Game;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Path={working_dir}
|
Path={working_dir}
|
||||||
Icon={icon_path}
|
Icon={icon_path}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return desktop_entry, desktop_path
|
return desktop_entry, desktop_path
|
||||||
|
@@ -11,6 +11,7 @@ from portprotonqt.image_utils import FullscreenDialog
|
|||||||
from portprotonqt.custom_widgets import NavLabel
|
from portprotonqt.custom_widgets import NavLabel
|
||||||
from portprotonqt.game_card import GameCard
|
from portprotonqt.game_card import GameCard
|
||||||
from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config
|
from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config
|
||||||
|
from portprotonqt.dialogs import AddGameDialog
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ class MainWindowProtocol(Protocol):
|
|||||||
gamesListWidget: QWidget
|
gamesListWidget: QWidget
|
||||||
currentDetailPage: QWidget | None
|
currentDetailPage: QWidget | None
|
||||||
current_exec_line: str | None
|
current_exec_line: str | None
|
||||||
current_add_game_dialog: QDialog | None
|
current_add_game_dialog: AddGameDialog | None
|
||||||
|
|
||||||
# Mapping of actions to evdev button codes, includes Xbox and PlayStation controllers
|
# Mapping of actions to evdev button codes, includes Xbox and PlayStation controllers
|
||||||
# https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
|
# https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
|
||||||
@@ -93,6 +94,21 @@ class InputManager(QObject):
|
|||||||
self.last_trigger_time = 0.0
|
self.last_trigger_time = 0.0
|
||||||
self.trigger_cooldown = 0.2
|
self.trigger_cooldown = 0.2
|
||||||
|
|
||||||
|
# FileExplorer specific attributes
|
||||||
|
self.file_explorer = None
|
||||||
|
self.original_button_handler = None
|
||||||
|
self.original_dpad_handler = None
|
||||||
|
self.original_gamepad_state = None
|
||||||
|
self.nav_timer = QTimer(self)
|
||||||
|
self.nav_timer.timeout.connect(self.handle_navigation_repeat)
|
||||||
|
self.current_direction = 0
|
||||||
|
self.last_nav_time = 0
|
||||||
|
self.initial_nav_delay = 0.1 # Начальная задержка перед первым повторением (сек)
|
||||||
|
self.repeat_nav_delay = 0.05 # Интервал между повторениями (сек)
|
||||||
|
self.stick_activated = False
|
||||||
|
self.stick_value = 0 # Текущее значение стика (для плавности)
|
||||||
|
self.dead_zone = 8000 # Мертвая зона стика
|
||||||
|
|
||||||
# Add variables for continuous D-pad movement
|
# Add variables for continuous D-pad movement
|
||||||
self.dpad_timer = QTimer(self)
|
self.dpad_timer = QTimer(self)
|
||||||
self.dpad_timer.timeout.connect(self.handle_dpad_repeat)
|
self.dpad_timer.timeout.connect(self.handle_dpad_repeat)
|
||||||
@@ -112,6 +128,114 @@ class InputManager(QObject):
|
|||||||
# Initialize evdev + hotplug
|
# Initialize evdev + hotplug
|
||||||
self.init_gamepad()
|
self.init_gamepad()
|
||||||
|
|
||||||
|
def enable_file_explorer_mode(self, file_explorer):
|
||||||
|
"""Настройка обработки геймпада для FileExplorer"""
|
||||||
|
try:
|
||||||
|
self.file_explorer = file_explorer
|
||||||
|
self.original_button_handler = self.handle_button_slot
|
||||||
|
self.original_dpad_handler = self.handle_dpad_slot
|
||||||
|
self.original_gamepad_state = self._gamepad_handling_enabled
|
||||||
|
self.handle_button_slot = self.handle_file_explorer_button
|
||||||
|
self.handle_dpad_slot = self.handle_file_explorer_dpad
|
||||||
|
self._gamepad_handling_enabled = True
|
||||||
|
logger.debug("Gamepad handling successfully connected for FileExplorer")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting gamepad handlers for FileExplorer: {e}")
|
||||||
|
|
||||||
|
def disable_file_explorer_mode(self):
|
||||||
|
"""Восстановление оригинальных обработчиков главного окна программы (дефолт возвращаем)"""
|
||||||
|
try:
|
||||||
|
if self.file_explorer:
|
||||||
|
self.handle_button_slot = self.original_button_handler
|
||||||
|
self.handle_dpad_slot = self.original_dpad_handler
|
||||||
|
self._gamepad_handling_enabled = self.original_gamepad_state
|
||||||
|
self.file_explorer = None
|
||||||
|
self.nav_timer.stop()
|
||||||
|
logger.debug("Gamepad handling successfully restored")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error restoring gamepad handlers: {e}")
|
||||||
|
|
||||||
|
def handle_file_explorer_button(self, button_code):
|
||||||
|
"""Обработка кнопок геймпада для FileExplorer"""
|
||||||
|
try:
|
||||||
|
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if button_code in BUTTONS['confirm']: # Кнопка A
|
||||||
|
self.file_explorer.select_item()
|
||||||
|
elif button_code in BUTTONS['back']: # Кнопка B
|
||||||
|
self.file_explorer.close()
|
||||||
|
else:
|
||||||
|
if self.original_button_handler:
|
||||||
|
self.original_button_handler(button_code)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in FileExplorer button handler: {e}")
|
||||||
|
|
||||||
|
def handle_file_explorer_dpad(self, code, value, current_time):
|
||||||
|
"""Обработка движения D-pad и левого стика для FileExplorer"""
|
||||||
|
try:
|
||||||
|
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list') or not self.file_explorer.file_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.file_explorer.file_list.count():
|
||||||
|
return
|
||||||
|
|
||||||
|
if code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y):
|
||||||
|
# Для D-pad - реакция с фиксированной скоростью
|
||||||
|
if code == ecodes.ABS_HAT0Y:
|
||||||
|
if value != 0:
|
||||||
|
self.current_direction = value
|
||||||
|
self.stick_value = 1.0 # Максимальная скорость для D-pad, чтобы скачков не было
|
||||||
|
if not self.nav_timer.isActive():
|
||||||
|
self.file_explorer.move_selection(self.current_direction)
|
||||||
|
self.last_nav_time = current_time
|
||||||
|
self.nav_timer.start(int(self.initial_nav_delay * 1000))
|
||||||
|
else:
|
||||||
|
self.current_direction = 0
|
||||||
|
self.nav_timer.stop()
|
||||||
|
|
||||||
|
# Для стика - плавное управление с учетом степени отклонения
|
||||||
|
elif code == ecodes.ABS_Y:
|
||||||
|
if abs(value) < self.dead_zone:
|
||||||
|
if self.stick_activated:
|
||||||
|
self.current_direction = 0
|
||||||
|
self.nav_timer.stop()
|
||||||
|
self.stick_activated = False
|
||||||
|
return
|
||||||
|
|
||||||
|
# Рассчитываем "силу" отклонения (0.3 - 1.0)
|
||||||
|
normalized_value = (abs(value) - self.dead_zone) / (32768 - self.dead_zone)
|
||||||
|
speed_factor = 0.3 + (normalized_value * 0.7) # От 30% до 100% скорости
|
||||||
|
self.current_direction = -1 if value < 0 else 1
|
||||||
|
self.stick_value = speed_factor
|
||||||
|
self.stick_activated = True
|
||||||
|
|
||||||
|
if not self.nav_timer.isActive():
|
||||||
|
self.file_explorer.move_selection(self.current_direction)
|
||||||
|
self.last_nav_time = current_time
|
||||||
|
self.nav_timer.start(int(self.initial_nav_delay * 1000))
|
||||||
|
|
||||||
|
elif self.original_dpad_handler:
|
||||||
|
self.original_dpad_handler(code, value, current_time)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in FileExplorer dpad handler: {e}")
|
||||||
|
|
||||||
|
def handle_navigation_repeat(self):
|
||||||
|
"""Плавное повторение движения с переменной скоростью для FileExplorer"""
|
||||||
|
try:
|
||||||
|
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list') or not self.file_explorer.file_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.current_direction != 0:
|
||||||
|
now = time.time()
|
||||||
|
# Динамический интервал в зависимости от stick_value
|
||||||
|
dynamic_delay = self.repeat_nav_delay / self.stick_value
|
||||||
|
if now - self.last_nav_time >= dynamic_delay:
|
||||||
|
self.file_explorer.move_selection(self.current_direction)
|
||||||
|
self.last_nav_time = now
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in navigation repeat: {e}")
|
||||||
|
|
||||||
@Slot(bool)
|
@Slot(bool)
|
||||||
def handle_fullscreen_slot(self, enable: bool) -> None:
|
def handle_fullscreen_slot(self, enable: bool) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -138,6 +262,7 @@ class InputManager(QObject):
|
|||||||
self._gamepad_handling_enabled = False
|
self._gamepad_handling_enabled = False
|
||||||
self.stop_rumble()
|
self.stop_rumble()
|
||||||
self.dpad_timer.stop()
|
self.dpad_timer.stop()
|
||||||
|
self.nav_timer.stop()
|
||||||
|
|
||||||
def enable_gamepad_handling(self) -> None:
|
def enable_gamepad_handling(self) -> None:
|
||||||
"""Включает обработку событий геймпада."""
|
"""Включает обработку событий геймпада."""
|
||||||
@@ -821,6 +946,7 @@ class InputManager(QObject):
|
|||||||
try:
|
try:
|
||||||
self.running = False
|
self.running = False
|
||||||
self.dpad_timer.stop()
|
self.dpad_timer.stop()
|
||||||
|
self.nav_timer.stop()
|
||||||
self.stop_rumble()
|
self.stop_rumble()
|
||||||
if self.gamepad_thread:
|
if self.gamepad_thread:
|
||||||
self.gamepad_thread.join()
|
self.gamepad_thread.join()
|
||||||
|
Binary file not shown.
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-06-24 11:17+0500\n"
|
"POT-Creation-Date: 2025-06-26 14:55+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -117,6 +117,10 @@ msgstr ""
|
|||||||
msgid "start.sh not found at {path}"
|
msgid "start.sh not found at {path}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Launch game \"{name}\" with PortProton"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to create .desktop file: {error}"
|
msgid "Failed to create .desktop file: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -260,6 +264,12 @@ msgstr ""
|
|||||||
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Select"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -281,22 +291,12 @@ msgstr ""
|
|||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Select Executable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Select Cover Image"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Invalid image"
|
msgid "Invalid image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No cover selected"
|
msgid "No cover selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
|
||||||
msgid "Launch game \"{name}\" with PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -616,9 +616,6 @@ msgstr ""
|
|||||||
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Failed to reboot the system"
|
msgid "Failed to reboot the system"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
Binary file not shown.
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-06-24 11:17+0500\n"
|
"POT-Creation-Date: 2025-06-26 14:55+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
@@ -117,6 +117,10 @@ msgstr ""
|
|||||||
msgid "start.sh not found at {path}"
|
msgid "start.sh not found at {path}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Launch game \"{name}\" with PortProton"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to create .desktop file: {error}"
|
msgid "Failed to create .desktop file: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -260,6 +264,12 @@ msgstr ""
|
|||||||
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Select"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -281,22 +291,12 @@ msgstr ""
|
|||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Select Executable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Select Cover Image"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Invalid image"
|
msgid "Invalid image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No cover selected"
|
msgid "No cover selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
|
||||||
msgid "Launch game \"{name}\" with PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -616,9 +616,6 @@ msgstr ""
|
|||||||
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Failed to reboot the system"
|
msgid "Failed to reboot the system"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PortProtonQt 0.1.1\n"
|
"Project-Id-Version: PortProtonQt 0.1.1\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-06-24 11:17+0500\n"
|
"POT-Creation-Date: 2025-06-26 14:55+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -115,6 +115,10 @@ msgstr ""
|
|||||||
msgid "start.sh not found at {path}"
|
msgid "start.sh not found at {path}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Launch game \"{name}\" with PortProton"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to create .desktop file: {error}"
|
msgid "Failed to create .desktop file: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -258,6 +262,12 @@ msgstr ""
|
|||||||
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Select"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -279,22 +289,12 @@ msgstr ""
|
|||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Select Executable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Select Cover Image"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Invalid image"
|
msgid "Invalid image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No cover selected"
|
msgid "No cover selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
|
||||||
msgid "Launch game \"{name}\" with PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -614,9 +614,6 @@ msgstr ""
|
|||||||
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Failed to reboot the system"
|
msgid "Failed to reboot the system"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
Binary file not shown.
@@ -9,8 +9,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-06-24 11:17+0500\n"
|
"POT-Creation-Date: 2025-06-26 14:55+0500\n"
|
||||||
"PO-Revision-Date: 2025-06-24 11:16+0500\n"
|
"PO-Revision-Date: 2025-06-26 14:55+0500\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
"Language-Team: ru_RU <LL@li.org>\n"
|
"Language-Team: ru_RU <LL@li.org>\n"
|
||||||
@@ -120,6 +120,10 @@ msgstr "'{game_name}' был(а) удалён(а) из избранного"
|
|||||||
msgid "start.sh not found at {path}"
|
msgid "start.sh not found at {path}"
|
||||||
msgstr "start.sh не найден по адресу {path}"
|
msgstr "start.sh не найден по адресу {path}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Launch game \"{name}\" with PortProton"
|
||||||
|
msgstr "Запустить игру \"{name}\" с помощью PortProton"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to create .desktop file: {error}"
|
msgid "Failed to create .desktop file: {error}"
|
||||||
msgstr "Не удалось создать файл .desktop: {error}"
|
msgstr "Не удалось создать файл .desktop: {error}"
|
||||||
@@ -267,6 +271,12 @@ msgstr "Не удалось удалить игру EGS '{game_name}' из Steam
|
|||||||
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
||||||
msgstr "Не удалось удалить игру '{game_name}' из Steam: {error}"
|
msgstr "Не удалось удалить игру '{game_name}' из Steam: {error}"
|
||||||
|
|
||||||
|
msgid "Select"
|
||||||
|
msgstr "Выбрать"
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Отмена"
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
msgstr "Редактировать игру"
|
msgstr "Редактировать игру"
|
||||||
|
|
||||||
@@ -288,22 +298,12 @@ msgstr "Обложка:"
|
|||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr "Предпросмотр обложки:"
|
msgstr "Предпросмотр обложки:"
|
||||||
|
|
||||||
msgid "Select Executable"
|
|
||||||
msgstr "Выберите исполняемый файл"
|
|
||||||
|
|
||||||
msgid "Select Cover Image"
|
|
||||||
msgstr "Выберите обложку"
|
|
||||||
|
|
||||||
msgid "Invalid image"
|
msgid "Invalid image"
|
||||||
msgstr "Недопустимое изображение"
|
msgstr "Недопустимое изображение"
|
||||||
|
|
||||||
msgid "No cover selected"
|
msgid "No cover selected"
|
||||||
msgstr "Обложка не выбрана"
|
msgstr "Обложка не выбрана"
|
||||||
|
|
||||||
#, python-brace-format
|
|
||||||
msgid "Launch game \"{name}\" with PortProton"
|
|
||||||
msgstr "Запустить игру \"{name}\" с помощью PortProton"
|
|
||||||
|
|
||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr "Загрузка игр из Epic Games Store..."
|
msgstr "Загрузка игр из Epic Games Store..."
|
||||||
|
|
||||||
@@ -625,9 +625,6 @@ msgstr "Вернуться на рабочий стол"
|
|||||||
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
msgid "portprotonqt-session-select file not found at /usr/bin/"
|
||||||
msgstr "portprotonqt-session-select не найдет"
|
msgstr "portprotonqt-session-select не найдет"
|
||||||
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr "Отмена"
|
|
||||||
|
|
||||||
msgid "Failed to reboot the system"
|
msgid "Failed to reboot the system"
|
||||||
msgstr "Не удалось перезагрузить систему"
|
msgstr "Не удалось перезагрузить систему"
|
||||||
|
|
||||||
|
@@ -646,3 +646,57 @@ SETTINGS_COMBO_STYLE = f"""
|
|||||||
background: rgba(0,122,255,0.25);
|
background: rgba(0,122,255,0.25);
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class FileExplorerStyles:
|
||||||
|
WINDOW_STYLE = """
|
||||||
|
QDialog {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: "Arial";
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PATH_LABEL_STYLE = """
|
||||||
|
QLabel {
|
||||||
|
color: #3daee9;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
LIST_STYLE = """
|
||||||
|
QListWidget {
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #353535;
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
QListWidget::item {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
QListWidget::item:selected {
|
||||||
|
background-color: #3daee9;
|
||||||
|
color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
BUTTON_STYLE = """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #3daee9;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #2c9fd8;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #1a8fc7;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
@@ -866,3 +866,57 @@ def detail_page_style(stops):
|
|||||||
border-radius: {border_radius_b};
|
border-radius: {border_radius_b};
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class FileExplorerStyles:
|
||||||
|
WINDOW_STYLE = """
|
||||||
|
QDialog {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: "Arial";
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PATH_LABEL_STYLE = """
|
||||||
|
QLabel {
|
||||||
|
color: #3daee9;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
LIST_STYLE = """
|
||||||
|
QListWidget {
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #353535;
|
||||||
|
color: #eee;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
QListWidget::item {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
QListWidget::item:selected {
|
||||||
|
background-color: #3daee9;
|
||||||
|
color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
BUTTON_STYLE = """
|
||||||
|
QPushButton {
|
||||||
|
background-color: #3daee9;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #2c9fd8;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #1a8fc7;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
Reference in New Issue
Block a user