diff --git a/app/ui/widgets/loading_spinner.py b/app/ui/widgets/loading_spinner.py new file mode 100644 index 0000000..dde26d6 --- /dev/null +++ b/app/ui/widgets/loading_spinner.py @@ -0,0 +1,67 @@ +from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtWidgets import QLabel +from PyQt6.QtGui import QPainter, QPen +from app.core.main_manager import MainManager +import math + +class LoadingSpinner(QLabel): + def __init__(self, size=40, parent=None): + super().__init__(parent) + self.size = size + self.angle = 0 + self.setFixedSize(size, size) + + # Timer pour l'animation + self.timer = QTimer() + self.timer.timeout.connect(self.rotate) + self.timer.start(50) # 50ms = rotation fluide + + def rotate(self): + self.angle = (self.angle + 10) % 360 + self.update() + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + + # Obtenir la couleur du thème + main_manager = MainManager.get_instance() + theme = main_manager.get_theme_manager().get_theme() + color = theme.get_color("background_secondary_color") + + # Dessiner le cercle de chargement + rect = self.rect() + center_x, center_y = rect.width() // 2, rect.height() // 2 + radius = min(center_x, center_y) - 5 + + painter.translate(center_x, center_y) + painter.rotate(self.angle) + + # Dessiner les segments du spinner + pen = QPen() + pen.setWidth(3) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + + for i in range(8): + alpha = 255 - (i * 25) # Dégradé d'opacité + pen.setColor(self.hex_to_qcolor(color, alpha)) + painter.setPen(pen) + + angle = i * 45 + start_x = radius * 0.7 * math.cos(math.radians(angle)) + start_y = radius * 0.7 * math.sin(math.radians(angle)) + end_x = radius * math.cos(math.radians(angle)) + end_y = radius * math.sin(math.radians(angle)) + + painter.drawLine(int(start_x), int(start_y), int(end_x), int(end_y)) + + def hex_to_qcolor(self, hex_color, alpha=255): + from PyQt6.QtGui import QColor + hex_color = hex_color.lstrip('#') + r = int(hex_color[0:2], 16) + g = int(hex_color[2:4], 16) + b = int(hex_color[4:6], 16) + return QColor(r, g, b, alpha) + + def stop(self): + self.timer.stop() \ No newline at end of file diff --git a/app/ui/windows/splash_screen.py b/app/ui/windows/splash_screen.py new file mode 100644 index 0000000..3bcc02a --- /dev/null +++ b/app/ui/windows/splash_screen.py @@ -0,0 +1,132 @@ +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel +from PyQt6.QtCore import Qt, QTimer, pyqtSignal +from PyQt6.QtGui import QPixmap +from app.core.main_manager import MainManager +from app.ui.widgets.loading_spinner import LoadingSpinner +import app.utils.paths as paths + +class SplashScreen(QWidget): + finished = pyqtSignal() + + def __init__(self, duration=3000, parent=None): + super().__init__(parent) + self.duration = duration + + self.main_manager = MainManager.get_instance() + self.theme_manager = self.main_manager.get_theme_manager() + self.settings_manager = self.main_manager.get_settings_manager() + + self.setup_ui() + self.setup_timer() + + + def setup_ui(self): + # Configuration de la fenêtre + self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + self.setFixedSize(800, 600) + + # Layout principal + layout = QVBoxLayout(self) + layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.setSpacing(60) + layout.setContentsMargins(80, 80, 80, 80) + + # Image splash + self.image_label = QLabel() + self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.load_splash_image() + layout.addWidget(self.image_label) + + # Spinner de chargement + self.spinner = LoadingSpinner(50, self) + spinner_layout = QVBoxLayout() + spinner_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + spinner_layout.addWidget(self.spinner) + layout.addLayout(spinner_layout) + + # Appliquer le thème + self.apply_theme() + + # Centrer la fenêtre + self.center_on_screen() + + def load_splash_image(self): + """Charge l'image splash depuis la config""" + try: + splash_image_path = paths.get_asset_path(self.settings_manager.get_config("splash_image")) + if splash_image_path: + # Essayer le chemin depuis la config + if not splash_image_path.startswith('/') and not splash_image_path.startswith('\\') and ':' not in splash_image_path: + # Chemin relatif, le résoudre depuis le dossier assets + splash_image_path = paths.get_asset_path(splash_image_path) + + pixmap = QPixmap(splash_image_path) + if not pixmap.isNull(): + # Redimensionner l'image + scaled_pixmap = pixmap.scaled(400, 300, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) + self.image_label.setPixmap(scaled_pixmap) + return + + # Fallback : essayer l'icône par défaut + fallback_path = paths.get_asset_path("icon.png") + pixmap = QPixmap(fallback_path) + if not pixmap.isNull(): + scaled_pixmap = pixmap.scaled(240, 240, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) + self.image_label.setPixmap(scaled_pixmap) + else: + # Dernier fallback : texte + self.image_label.setText("🚀") + self.image_label.setStyleSheet("font-size: 48px;") + except Exception: + # En cas d'erreur, afficher un emoji + self.image_label.setText("🚀") + self.image_label.setStyleSheet("font-size: 48px;") + + def apply_theme(self): + """Applique le thème actuel""" + theme = self.theme_manager.get_theme() + + style = f""" + QWidget {{ + background-color: {theme.get_color("background_color")}; + border-radius: 15px; + border: 2px solid {theme.get_color("primary_color")}; + }} + QLabel {{ + color: {theme.get_color("text_color")}; + background: transparent; + border: none; + }} + """ + self.setStyleSheet(style) + + def center_on_screen(self): + """Centre la fenêtre sur l'écran""" + from PyQt6.QtWidgets import QApplication + screen = QApplication.primaryScreen() + screen_geometry = screen.geometry() + + x = (screen_geometry.width() - self.width()) // 2 + y = (screen_geometry.height() - self.height()) // 2 + self.move(x, y) + + def setup_timer(self): + """Configure le timer pour fermer automatiquement le splash screen""" + self.timer = QTimer() + self.timer.timeout.connect(self.close_splash) + self.timer.start(self.duration) + + def close_splash(self): + """Ferme le splash screen et émet le signal""" + if hasattr(self, 'spinner'): + self.spinner.stop() + self.timer.stop() + self.hide() + self.finished.emit() + + def show_splash(self): + """Affiche le splash screen""" + self.show() + self.raise_() + self.activateWindow() \ No newline at end of file diff --git a/config.json b/config.json index c51be8f..2735aaa 100644 --- a/config.json +++ b/config.json @@ -4,6 +4,7 @@ "app_version": "1.0.0", "architecture": "x64", "icon_path": "data/assets/icon.ico", + "splash_image": "splash", "main_script": "main.py", "git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate" } \ No newline at end of file diff --git a/data/assets/icon.icns b/data/assets/icon.icns new file mode 100644 index 0000000..69fd967 Binary files /dev/null and b/data/assets/icon.icns differ diff --git a/data/assets/icon.ico b/data/assets/icon.ico index ab5551c..2e25305 100644 Binary files a/data/assets/icon.ico and b/data/assets/icon.ico differ diff --git a/data/assets/icon.png b/data/assets/icon.png index 139ce31..76b8d9a 100644 Binary files a/data/assets/icon.png and b/data/assets/icon.png differ diff --git a/data/assets/splash.png b/data/assets/splash.png new file mode 100644 index 0000000..bdb7096 Binary files /dev/null and b/data/assets/splash.png differ diff --git a/main.py b/main.py index 065c26f..290ded6 100644 --- a/main.py +++ b/main.py @@ -3,23 +3,52 @@ import app.utils.paths as paths from PyQt6.QtWidgets import QApplication from PyQt6.QtGui import QIcon from app.ui.main_window import MainWindow +from app.ui.windows.splash_screen import SplashScreen from app.core.main_manager import MainManager + def main() -> int: main_manager: MainManager = MainManager.get_instance() theme_manager = main_manager.get_theme_manager() settings_manager = main_manager.get_settings_manager() - update_manager = main_manager.get_update_manager() # Ajout + update_manager = main_manager.get_update_manager() app: QApplication = QApplication(sys.argv) - app.setStyleSheet(theme_manager.get_sheet()) app.setApplicationName(settings_manager.get_config("app_name")) app.setWindowIcon(QIcon(paths.get_asset_path("icon"))) - if update_manager.check_for_update(): - return + # Vérifier si l'image splash existe + splash_image_path = paths.get_asset_path(settings_manager.get_config("splash_image")) + use_splash = splash_image_path and paths.Path(splash_image_path).exists() - window: MainWindow = MainWindow() - window.show() + if use_splash: + # Créer et afficher le splash screen + splash = SplashScreen(duration=1500) + splash.show_splash() + + # Vérifier les mises à jour en arrière-plan + if update_manager.check_for_update(): + splash.close_splash() + return 0 + + # Créer la fenêtre principale + window: MainWindow = MainWindow() + + # Connecter le signal finished pour afficher la fenêtre principale + def show_main_window(): + splash.close() + window.show() + + splash.finished.connect(show_main_window) + else: + # Pas de splash screen, vérifier directement les mises à jour + if update_manager.check_for_update(): + return 0 + + # Créer et afficher directement la fenêtre principale + window: MainWindow = MainWindow() + window.show() + + app.setStyleSheet(theme_manager.get_sheet()) return app.exec() if __name__ == "__main__":