diff --git a/src/ui/main_window.py b/src/ui/main_window.py index df1843f..ddb34d8 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -15,11 +15,10 @@ def __init__(self, config_manager=None): self.input_manager = None # Настройка прозрачного и безрамочного окна - self.setWindowFlags( - Qt.WindowStaysOnTopHint | - Qt.FramelessWindowHint | - Qt.Tool - ) + flags = Qt.FramelessWindowHint | Qt.Tool + if not self.config or self.config.get("always_on_top"): + flags |= Qt.WindowStaysOnTopHint + self.setWindowFlags(flags) self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowOpacity(self.config.get("opacity") / 100.0 if self.config else 1.0) @@ -88,6 +87,17 @@ def moveEvent(self, event): def get_cached_pos(self): return self._cached_pos + def set_always_on_top(self, enabled): + """Включает или выключает отображение поверх всех окон.""" + flags = self.windowFlags() + if enabled: + flags |= Qt.WindowStaysOnTopHint + else: + flags &= ~Qt.WindowStaysOnTopHint + self.setWindowFlags(flags) + if self.isVisible(): + self.show() + def set_opacity(self, value): """Устанавливает прозрачность окна (0-100)""" self.setWindowOpacity(value / 100.0) diff --git a/src/ui/settings_dialog.py b/src/ui/settings_dialog.py index 17c1a0f..e91ed2e 100644 --- a/src/ui/settings_dialog.py +++ b/src/ui/settings_dialog.py @@ -1,4 +1,4 @@ -from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSlider, QComboBox, QPushButton, QSpinBox +from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSlider, QComboBox, QPushButton, QSpinBox, QCheckBox from PySide6.QtCore import Qt from src.utils.bonding_utils import CAT_SKINS @@ -16,6 +16,11 @@ def __init__(self, config, parent=None): self.name_edit = QLineEdit(self.config.get("username")) layout.addWidget(self.name_edit) + # Поверх всех окон + self.always_on_top_cb = QCheckBox("Поверх всех окон") + self.always_on_top_cb.setChecked(self.config.get("always_on_top")) + layout.addWidget(self.always_on_top_cb) + # Громкость layout.addWidget(QLabel("Громкость звука:")) self.volume_slider = QSlider(Qt.Horizontal) @@ -74,6 +79,7 @@ def __init__(self, config, parent=None): def save_settings(self): self.config.set("username", self.name_edit.text()) + self.config.set("always_on_top", self.always_on_top_cb.isChecked()) self.config.set("volume", self.volume_slider.value()) self.config.set("opacity", self.opacity_slider.value()) self.config.set("stretch_interval", self.stretch_spin.value()) diff --git a/src/ui/tray_menu.py b/src/ui/tray_menu.py index c7ca788..7a71b31 100644 --- a/src/ui/tray_menu.py +++ b/src/ui/tray_menu.py @@ -174,8 +174,9 @@ 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_always_on_top(self.window.config.get("always_on_top")) self.window.set_opacity(self.window.config.get("opacity")) # Обновляем тексты в меню Pomodoro self.update_pomodoro_menu_texts() diff --git a/tests/test_utils.py b/tests/test_utils.py index e363433..51b599c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,12 +11,21 @@ import os import json import time +from PySide6.QtCore import Qt +from src.ui.main_window import PetWindow from src.utils.config_manager import ConfigManager from src.utils.data_store import DataStore from src.core.timer_system import TimerSystem from src.core.input_manager import InputManager class TestUtils(unittest.TestCase): + @classmethod + def setUpClass(cls): + from PySide6.QtWidgets import QApplication + # Initialize offscreen app for testing PetWindow + os.environ["QT_QPA_PLATFORM"] = "offscreen" + cls.app = QApplication.instance() or QApplication(sys.argv) + def setUp(self): self.config_path = "test_settings.json" self.db_path = "test_activity.db" @@ -86,6 +95,21 @@ def test_timer_system_pomodoro(self): self.assertEqual(ts.pomodoro_state, "idle") self.assertEqual(ts.pomodoro_remaining, 0) + def test_pet_window_always_on_top(self): + config = ConfigManager(self.config_path) + # Test initial state with always_on_top = True (default) + config.set("always_on_top", True) + window = PetWindow(config) + self.assertTrue(bool(window.windowFlags() & Qt.WindowStaysOnTopHint)) + + # Test disabling always_on_top dynamically + window.set_always_on_top(False) + self.assertFalse(bool(window.windowFlags() & Qt.WindowStaysOnTopHint)) + + # Test enabling always_on_top dynamically + window.set_always_on_top(True) + self.assertTrue(bool(window.windowFlags() & Qt.WindowStaysOnTopHint)) + def test_input_manager_petting_logic(self): # Мокаем PetWindow, AnimationManager и SoundManager mock_window = MagicMock() diff --git a/verification/screenshots/stats_history.png b/verification/screenshots/stats_history.png index 9dbf806..1be1d5e 100644 Binary files a/verification/screenshots/stats_history.png and b/verification/screenshots/stats_history.png differ