diff --git a/winehelper_gui.py b/winehelper_gui.py index 63e6435..c01851a 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -13,7 +13,7 @@ from functools import partial from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget, QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser) -from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment +from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter @@ -908,68 +908,79 @@ class WineHelperGUI(QMainWindow): print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}") return None - def _ease_in_out_quad(self, t): - """Простая квадратичная функция сглаживания (ease-in-out).""" - # t - значение от 0.0 до 1.0 - if t < 0.5: - return 2 * t * t - return 1 - pow(-2 * t + 2, 2) / 2 - def _start_icon_fade_animation(self, button): - """Запускает анимацию плавного перехода для иконки на кнопке.""" + """Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation.""" if button not in self.icon_animators: return anim_data = self.icon_animators[button] + # Получаем или создаем объект анимации один раз + animation = anim_data.get('animation') + if not animation: + # Устанавливаем динамическое свойство, чтобы избежать предупреждений + button.setProperty("fadeProgress", 0.0) + animation = QPropertyAnimation(button, b"fadeProgress", self) + animation.setDuration(700) + animation.setEasingCurve(QEasingCurve.InOutQuad) + + # Сигналы подключаются только один раз при создании + animation.valueChanged.connect( + lambda value, b=button: self._update_icon_frame(b, value) + ) + animation.finished.connect( + lambda b=button: self._on_fade_animation_finished(b) + ) + anim_data['animation'] = animation + # Останавливаем предыдущую анимацию, если она еще идет - if anim_data.get('fade_timer') and anim_data['fade_timer'].isActive(): - anim_data['fade_timer'].stop() + if animation.state() == QPropertyAnimation.Running: + animation.stop() # Определяем текущую и следующую иконки current_icon_path = anim_data['icons'][anim_data['current_index']] next_icon_index = (anim_data['current_index'] + 1) % len(anim_data['icons']) next_icon_path = anim_data['icons'][next_icon_index] - current_pixmap = QPixmap(current_icon_path) - next_pixmap = QPixmap(next_icon_path) + # Сохраняем QPixmap для использования в функции обновления кадра + anim_data['pixmaps'] = (QPixmap(current_icon_path), QPixmap(next_icon_path)) - # Запускаем таймер для покадровой анимации - anim_data['fade_step'] = 0 - fade_timer = QTimer(button) - anim_data['fade_timer'] = fade_timer - fade_timer.timeout.connect( - lambda b=button, p1=current_pixmap, p2=next_pixmap: self._update_fade_frame(b, p1, p2) - ) - fade_timer.start(16) # ~60 кадров в секунду + # Устанавливаем начальное и конечное значения и запускаем + animation.setStartValue(0.0) + animation.setEndValue(1.0) + animation.start() # Без DeleteWhenStopped - def _update_fade_frame(self, button, old_pixmap, new_pixmap): - """Обновляет кадр анимации перехода иконок.""" - if button not in self.icon_animators: + def _on_fade_animation_finished(self, button): + """Вызывается по завершении анимации для обновления индекса иконки.""" + if button in self.icon_animators: + anim_data = self.icon_animators[button] + anim_data['current_index'] = (anim_data['current_index'] + 1) % len(anim_data['icons']) + + def _update_icon_frame(self, button, progress): + """Обновляет кадр анимации, смешивая две иконки в зависимости от прогресса.""" + anim_data = self.icon_animators.get(button) + if not anim_data or 'pixmaps' not in anim_data: return - anim_data = self.icon_animators[button] - # Длительность анимации: 44 шага * 16 мс ~= 700 мс - FADE_DURATION_STEPS = 44 + old_pixmap, new_pixmap = anim_data['pixmaps'] - anim_data['fade_step'] += 1 - progress_linear = anim_data['fade_step'] / FADE_DURATION_STEPS - # Применяем функцию сглаживания для более плавного старта и завершения - progress = self._ease_in_out_quad(progress_linear) - - if progress_linear >= 1.0: - anim_data['fade_timer'].stop() - anim_data['current_index'] = (anim_data['current_index'] + 1) % len(anim_data['icons']) + # На последнем кадре просто устанавливаем новую иконку + if progress >= 1.0: button.setIcon(QIcon(new_pixmap)) return + # Создаем холст для отрисовки смешанной иконки canvas = QPixmap(button.iconSize()) canvas.fill(Qt.transparent) painter = QPainter(canvas) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) + + # Плавно скрываем старую иконку painter.setOpacity(1.0 - progress) painter.drawPixmap(canvas.rect(), old_pixmap) + + # Плавно проявляем новую иконку painter.setOpacity(progress) painter.drawPixmap(canvas.rect(), new_pixmap) painter.end()