Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/core/input_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,12 @@ def handle_mouse(self, x, y):
dy_pet = y - center_y
dist_sq_pet = dx_pet * dx_pet + dy_pet * dy_pet

# Оптимизация: сравнение квадрата расстояния (порог 60px -> 3600)
if dist_sq_pet < 3600:
# Динамический порог радиуса поглаживания на основе текущего размера окна (по умолчанию ~60px при 100px окне)
pet_radius = max(30, int(max(self.window.width(), self.window.height()) * 0.6))
threshold_sq = pet_radius * pet_radius

# Оптимизация: сравнение квадрата расстояния с динамическим порогом
if dist_sq_pet < threshold_sq:
# Исключаем пассивный фарм (требуем активное поглаживание: активное движение мыши и кулдаун)
if self.last_pet_time == 0:
self.last_pet_mouse_pos = (x, y)
Expand Down
12 changes: 11 additions & 1 deletion src/ui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def __init__(self, config_manager=None):
self.is_dragging = False
self.shake_count = 0
self.last_shake_time = 0
self.original_size = QSize(100, 100)

pet_size = self.config.get("pet_size") if self.config else 100
self.original_size = QSize(pet_size, pet_size)

# Начальный размер
self.resize(self.original_size)
Expand Down Expand Up @@ -91,6 +93,14 @@ def set_opacity(self, value):
"""Устанавливает прозрачность окна (0-100)"""
self.setWindowOpacity(value / 100.0)

def set_pet_size(self, size):
"""Устанавливает базовый размер питомца в пикселях (50-250)"""
if self.config:
self.config.set("pet_size", size)
self.original_size = QSize(size, size)
self.resize(self.original_size)
self.animation_manager.update_size(self.size())

def set_always_on_top(self, enabled):
"""Включает или выключает режим 'Поверх всех окон' динамически"""
if self.config:
Expand Down
16 changes: 16 additions & 0 deletions src/ui/settings_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ def __init__(self, config, parent=None):
self.opacity_slider.valueChanged.connect(lambda v: self.opacity_val_label.setText(f"{v}%"))
layout.addWidget(self.opacity_slider)

# Размер питомца
size_header_layout = QHBoxLayout()
size_header_layout.addWidget(QLabel("Размер питомца:"))
self.size_val_label = QLabel(f"{self.config.get('pet_size')}px")
self.size_val_label.setStyleSheet("font-weight: bold; color: #555;")
size_header_layout.addStretch()
size_header_layout.addWidget(self.size_val_label)
layout.addLayout(size_header_layout)

self.size_slider = QSlider(Qt.Horizontal)
self.size_slider.setRange(50, 250)
self.size_slider.setValue(self.config.get("pet_size"))
self.size_slider.valueChanged.connect(lambda v: self.size_val_label.setText(f"{v}px"))
layout.addWidget(self.size_slider)

# Интервал растяжки
layout.addWidget(QLabel("Интервал растяжки (мин):"))
self.stretch_spin = QSpinBox()
Expand Down Expand Up @@ -168,6 +183,7 @@ def save_settings(self):
self.config.set("always_on_top", self.always_on_top_check.isChecked())
self.config.set("volume", self.volume_slider.value())
self.config.set("opacity", self.opacity_slider.value())
self.config.set("pet_size", self.size_slider.value())
self.config.set("stretch_interval", self.stretch_spin.value())
self.config.set("pomodoro_work", self.pomodoro_work_spin.value())
self.config.set("pomodoro_break", self.pomodoro_break_spin.value())
Expand Down
10 changes: 8 additions & 2 deletions src/ui/tray_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ def start_break_timer(self, checked=False):
def show_settings(self, checked=False):
dialog = SettingsDialog(self.window.config, self.window)
if dialog.exec():
# Обновляем скин и прозрачность в реальном времени
# Обновляем скин, прозрачность и размер в реальном времени
self.window.animation_manager.set_skin(self.window.config.get("skin"))
self.window.set_opacity(self.window.config.get("opacity"))
self.window.set_pet_size(self.window.config.get("pet_size"))
# Обновляем меню скинов
self.update_skin_menu()
# Обновляем режим "Поверх всех окон" в реальном времени
Expand Down Expand Up @@ -242,5 +243,10 @@ def update_skin_menu(self):
self.skin_menu.clear()
for skin_id, name in CAT_SKINS.items():
action = QAction(name, self)
action.triggered.connect(lambda checked=False, sid=skin_id: self.window.animation_manager.set_skin(sid))
action.triggered.connect(lambda checked=False, sid=skin_id: self.select_skin(sid))
self.skin_menu.addAction(action)

def select_skin(self, skin_id):
self.window.animation_manager.set_skin(skin_id)
if self.window.config:
self.window.config.set("skin", skin_id)
3 changes: 2 additions & 1 deletion src/utils/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class ConfigManager:
"volume": 70,
"language": "ru",
"skin": "default",
"opacity": 100
"opacity": 100,
"pet_size": 100
}

def __init__(self, config_path="settings.json"):
Expand Down
54 changes: 54 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,53 @@ def test_always_on_top_logic(self):
self.assertFalse(bool(flags & Qt.WindowStaysOnTopHint))
self.assertFalse(config.get("always_on_top"))

window.close()

def test_pet_size_configuration_and_window(self):
os.environ["QT_QPA_PLATFORM"] = "offscreen"
from PySide6.QtWidgets import QApplication
app = QApplication.instance() or QApplication([])

config = ConfigManager(self.config_path)

# 1. Проверяем дефолтное значение pet_size в ConfigManager
self.assertEqual(config.get("pet_size"), 100)

# 2. Установка нового размера в ConfigManager
config.set("pet_size", 150)
self.assertEqual(config.get("pet_size"), 150)

# 3. Проверяем геометрию PetWindow
window = PetWindow(config)
self.assertEqual(window.original_size.width(), 150)
self.assertEqual(window.original_size.height(), 150)

# 4. Динамическое изменение размера окна через set_pet_size
window.set_pet_size(200)
self.assertEqual(window.original_size.width(), 200)
self.assertEqual(window.original_size.height(), 200)
self.assertEqual(config.get("pet_size"), 200)

window.close()

def test_tray_menu_select_skin_saves_config(self):
os.environ["QT_QPA_PLATFORM"] = "offscreen"
from PySide6.QtWidgets import QApplication
from src.ui.tray_menu import TrayMenu

app = QApplication.instance() or QApplication([])

config = ConfigManager(self.config_path)
window = PetWindow(config)
tray = TrayMenu(window)

# Выбор скина через трей должен немедленно записать его в конфиг
tray.select_skin("orange")
self.assertEqual(window.animation_manager.skin, "orange")
self.assertEqual(config.get("skin"), "orange")

window.close()

def test_config_manager(self):
config = ConfigManager(self.config_path)
config.set("username", "TestUser")
Expand Down Expand Up @@ -306,6 +353,8 @@ def test_custom_skins_integration(self):
self.assertEqual(anim_mgr.skin, "custom_test_skin")
self.assertTrue(anim_mgr.current_anim_path.endswith("cat_custom_test_skin.svg"))

label.close()

# Очистка
if os.path.exists(test_svg_path):
os.remove(test_svg_path)
Expand Down Expand Up @@ -356,6 +405,8 @@ def test_delete_custom_skin(self):
self.assertNotIn("custom_todel_skin", CAT_SKINS)
self.assertEqual(config.get("skin"), "default")
self.assertNotIn("custom_todel_skin", config.get("custom_skins") or {})

dialog.close()
finally:
# Восстанавливаем моки гарантированно
QMessageBox.question = original_question
Expand Down Expand Up @@ -412,6 +463,8 @@ def test_animation_manager_current_fps(self):
am.current_fps = 15
self.assertEqual(am.current_fps, 15)

label.close()

def test_sound_manager_fallback(self):
from src.utils.sound_manager import SoundManager
from PySide6.QtMultimedia import QSoundEffect
Expand Down Expand Up @@ -514,6 +567,7 @@ def test_stats_dialog_reset_ui_flow(self):
finally:
QMessageBox.question = original_question
QMessageBox.information = original_information
dialog.close()
db.close()

if __name__ == '__main__':
Expand Down
Binary file modified verification/screenshots/stats_history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.