Refacto à 100% pour la maintenabilité

This commit is contained in:
2026-03-13 13:45:49 +01:00
parent a6d4a708d8
commit e1b32688b4

View File

@@ -1,5 +1,4 @@
import sys
import os
import webbrowser
from pathlib import Path
from PySide6 import QtGui
@@ -20,6 +19,24 @@ import resources as resources # This is generated from the .qrc file # noqa:
# Remove this into final release
from fake_patch_notes import patch_note
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
URLS = {
"discord": "https://discord.gg/A7eanmSkp2",
"intranet": "https://la-taniere.fun/connexion/",
}
GLOW_COLOR = QColor(255, 140, 0, 255)
GLOW_BLUR_BASE = 15
GLOW_BLUR_PEAK = 70
GLOW_ANIM_DURATION = 1200
NO_STAFF = True
# ---------------------------------------------------------------------------
# Bundle path resolution
# ---------------------------------------------------------------------------
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
bundle_dir = Path(sys._MEIPASS)
else:
@@ -28,9 +45,9 @@ else:
# charger le fichier rcc compilé
QResource.registerResource(f"{bundle_dir}/resources.py")
NO_STAFF = True
CURRENT = os.path.dirname(os.path.realpath(__file__))
# ---------------------------------------------------------------------------
# Font helper
# ---------------------------------------------------------------------------
def load_custom_font() -> str:
# Load font from Qt resource
font_id = QFontDatabase.addApplicationFont(":/assets/Avocado-Cake-Demo.otf")
@@ -43,6 +60,154 @@ def load_custom_font() -> str:
raise RuntimeError("No font families found in the loaded font.")
return font_families[0]
# ---------------------------------------------------------------------------
# AudioController
# ---------------------------------------------------------------------------
class AudioController:
# Encapsule toute la logique audio : lecture, volume, mute.
def __init__(self, config: ConfigManager, slider, mute_btn):
self._config = config
self._slider = slider
self._mute_btn = mute_btn
# Lecteur
self._player = QMediaPlayer()
self._output = QAudioOutput()
self._player.setAudioOutput(self._output)
self._player.setLoops(-1)
# Chargement du MP3 depuis les ressources Qt
mp3file = QFile(":/assets/the-beat-of-nature.mp3")
mp3file.open(QFile.ReadOnly)
mp3data = mp3file.readAll()
mp3file.close()
self._buffer = QBuffer()
self._buffer.setData(QByteArray(mp3data))
self._buffer.open(QIODevice.ReadOnly)
self._player.setSourceDevice(self._buffer)
# État initial
volume = config.get_volume()
self._is_muted = volume == 0
self._previous_volume = (
volume if volume != 0 else config.get_default(VOLUME_KEY)
)
self._apply_volume(volume, save=False)
self._refresh_mute_btn()
self._player.play()
# Connexions
self._slider.valueChanged.connect(self._on_slider_changed)
self._mute_btn.clicked.connect(self.toggle_mute)
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def toggle_mute(self) -> None:
if not self._is_muted:
self._previous_volume = self._slider.value()
self._apply_volume(0)
self._is_muted = True
else:
self._apply_volume(self._previous_volume)
self._is_muted = False
# ------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------
def _on_slider_changed(self, value: int) -> None:
# Appelé quand l'utilisateur bouge le slider directement.
self._is_muted = (value == 0)
self._output.setVolume(value / 100.0)
self._config.set_volume(value)
self._refresh_mute_btn()
def _apply_volume(self, value: int, save: bool = True) -> None:
# Applique le volume sur le slider, l'output et la config.
# Bloquer le signal pour éviter une double mise à jour
self._slider.blockSignals(True)
self._slider.setValue(value)
self._slider.blockSignals(False)
self._output.setVolume(value / 100.0)
if save:
self._config.set_volume(value)
self._refresh_mute_btn()
def _refresh_mute_btn(self) -> None:
muted = self._slider.value() == 0
self._mute_btn.setProperty("muted", muted)
self._mute_btn.style().unpolish(self._mute_btn)
self._mute_btn.style().polish(self._mute_btn)
# ---------------------------------------------------------------------------
# GlowAnimator
# ---------------------------------------------------------------------------
class GlowAnimator:
# Gère l'effet de lueur pulsée sur un widget.
def __init__(self, widget):
self._widget = widget
self._effect = QGraphicsDropShadowEffect(widget)
self._effect.setBlurRadius(GLOW_BLUR_BASE)
self._effect.setOffset(0, 0)
self._effect.setColor(GLOW_COLOR)
self._anim = QPropertyAnimation(self._effect, b"blurRadius")
self._anim.setDuration(GLOW_ANIM_DURATION)
self._anim.setStartValue(GLOW_BLUR_BASE)
self._anim.setKeyValueAt(0.5, GLOW_BLUR_PEAK)
self._anim.setEndValue(GLOW_BLUR_BASE)
self._anim.setEasingCurve(QEasingCurve.InOutQuad)
self._anim.setLoopCount(-1)
def start(self) -> None:
self._widget.setGraphicsEffect(self._effect)
self._anim.start()
def stop(self) -> None:
self._anim.stop()
self._widget.setGraphicsEffect(None)
# ---------------------------------------------------------------------------
# WindowDragger
# ---------------------------------------------------------------------------
class WindowDragger:
# Permet de déplacer une fenêtre sans barre de titre.
def __init__(self, window: QMainWindow):
self._window = window
self._drag_pos = None
def mouse_press(self, event: QtGui.QMouseEvent) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self._drag_pos = (
event.globalPosition().toPoint()
- self._window.frameGeometry().topLeft()
)
def mouse_move(self, event: QtGui.QMouseEvent) -> None:
if event.buttons() & Qt.MouseButton.LeftButton and self._drag_pos is not None:
self._window.move(event.globalPosition().toPoint() - self._drag_pos)
def mouse_release(self, _event) -> None:
self._drag_pos = None
# ---------------------------------------------------------------------------
# MainWindow
# ---------------------------------------------------------------------------
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
@@ -50,195 +215,69 @@ class MainWindow(QMainWindow):
# Initialisation de la configuration
self.config = ConfigManager()
# Chargement de l'interface
# UI
self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow.ui", self)
self.setCentralWidget(self.ui.centralWidget())
# Remove the title bar and window frame
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
# Optional: Make background transparent (if you want rounded corners, etc.)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# Initialize audio
self.media_player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.media_player.setAudioOutput(self.audio_output)
self.media_player.setLoops(-1)
# Using mp3 in resource
mp3file = QFile(":/assets/the-beat-of-nature.mp3")
mp3file.open(QFile.ReadOnly)
mp3data = mp3file.readAll()
mp3file.close()
# Buffer mémoire
mp3buffer = QBuffer()
mp3buffer.setData(QByteArray(mp3data))
mp3buffer.open(QIODevice.ReadOnly)
self.mp3buffer = mp3buffer
self.media_player.setSourceDevice(self.mp3buffer)
volume = self.config.get_volume()
# Définir la valeur initiale du slider (ici: 10%)
self.ui.audio_volume_adjust.setValue(volume)
self.audio_output.setVolume(volume / 100)
self.is_muted = volume == 0
self.previous_volume = volume if volume != 0 else self.config.get_default(VOLUME_KEY)
if self.is_muted:
self.ui.mute_btn.setProperty("muted", True)
# Connexion du Slider
self.ui.audio_volume_adjust.valueChanged.connect(self.update_volume)
# Lancer la lecture (par exemple ici, ou dans une fonction)
self.media_player.play()
# Track mouse position for dragging
self._drag_pos = None
if NO_STAFF :
if NO_STAFF:
self.ui.staff_btn.hide()
self.ui.spacer_substitution.hide()
self.ui.info_text.setMarkdown(patch_note)
self.setup_button_animation()
self.connect_signals()
# Sous-systèmes
self._audio = AudioController(self.config, self.ui.audio_volume_adjust, self.ui.mute_btn)
self._glow = GlowAnimator(self.ui.connexion_btn)
self._dragger = WindowDragger(self)
# On centre la fenêtre avant de l'afficher
self.center_window()
self._connect_signals()
self._center_window()
self.show()
#------------------------------------------------------------------------------------------------------------
def setup_button_animation(self):
# 1. Création de l'effet de lueur intense
glow = QGraphicsDropShadowEffect(self.ui.connexion_btn)
glow.setBlurRadius(20) # Point de départ
glow.setOffset(0, 0) # Centré
glow.setColor(QColor(255, 140, 0, 255)) # Orange pur (Alpha max)
self.ui.connexion_btn.glow = glow
# 2. Animation du "Pulse" avec une grande amplitude
anim = QPropertyAnimation(glow, b"blurRadius")
anim.setDuration(1200) # Un peu plus rapide pour le dynamisme
anim.setStartValue(15) # Lueur de base
anim.setKeyValueAt(0.5, 70) # Lueur d'explosion (très large)
anim.setEndValue(15)
anim.setEasingCurve(QEasingCurve.InOutQuad)
anim.setLoopCount(-1)
self.ui.connexion_btn.pulse_anim = anim
def connect_signals(self):
# ------------------------------------------------------------------
# Setup
# ------------------------------------------------------------------
def _connect_signals(self) -> None:
self.ui.close_btn.clicked.connect(self.close)
self.ui.minimize_btn.clicked.connect(self.showMinimized)
self.ui.connexion_btn.clicked.connect(self.connexion_btn_link)
self.ui.discord_btn.clicked.connect(self.discord_btn_link)
self.ui.intranet_btn.clicked.connect(self.intranet_btn_link)
self.ui.mute_btn.clicked.connect(self.mute_btn_link)
self.ui.connexion_btn.clicked.connect(self._on_connexion)
self.ui.discord_btn.clicked.connect(self._on_discord)
self.ui.intranet_btn.clicked.connect(self._on_intranet)
# Le bouton mute est connecté dans la classe AudioController
#self.ui.mute_btn.clicked.connect(self.mute_btn_link)
def set_volume(self, value):
self.audio_output.setVolume(value / 100)
self.ui.audio_volume_adjust.setValue(value)
muted = value == 0
self.ui.mute_btn.setProperty("muted", muted)
self.ui.mute_btn.style().unpolish(self.ui.mute_btn)
self.ui.mute_btn.style().polish(self.ui.mute_btn)
def update_volume(self, value):
# 'value' est l'entier venant du slider (ex: 0 à 100)
# On convertit en float pour QAudioOutput (0.0 à 1.0)
volume = value / 100.0
self.audio_output.setVolume(volume)
self.config.set_volume(value)
if value == 0:
self.ui.mute_btn.setProperty("muted", True)
else:
self.ui.mute_btn.setProperty("muted", False)
self.ui.mute_btn.style().unpolish(self.ui.mute_btn)
self.ui.mute_btn.style().polish(self.ui.mute_btn)
def mute_btn_link(self) -> None:
current_volume = self.ui.audio_volume_adjust.value()
if current_volume == 0 and self.is_muted is False:
# volume déjà à 0 → restaurer le volume par défaut
default_volume = self.config.get_default(VOLUME_KEY)
self.ui.audio_volume_adjust.setValue(default_volume)
self.audio_output.setVolume(default_volume / 100)
self.config.set_volume(default_volume)
self.ui.mute_btn.setStyleSheet("")
return
if not self.is_muted:
# --- PASSAGE EN MUTE ---
self.previous_volume = current_volume
self.audio_output.setVolume(0.0)
self.ui.audio_volume_adjust.setValue(0)
self.is_muted = True
self.config.set_volume(0)
else:
# --- RESTAURATION ---
volume_float = self.previous_volume / 100
self.audio_output.setVolume(volume_float)
self.ui.audio_volume_adjust.setValue(self.previous_volume)
self.ui.mute_btn.setStyleSheet("")
self.is_muted = False
self.config.set_volume(self.previous_volume)
@staticmethod
def connexion_btn_link():
return None
@staticmethod
def discord_btn_link():
webbrowser.open('https://discord.gg/A7eanmSkp2')
def intranet_btn_link(self):
webbrowser.open('https://la-taniere.fun/connexion/')
self.ui.connexion_btn.setGraphicsEffect(self.ui.connexion_btn.glow)
self.ui.connexion_btn.pulse_anim.start()
def center_window(self):
def _center_window(self) -> None:
# On s'assure que la fenêtre a calculé sa taille
self.adjustSize()
screen = QtGui.QGuiApplication.screenAt(QtGui.QCursor.pos()) \
or QtGui.QGuiApplication.primaryScreen()
rect = self.frameGeometry()
rect.moveCenter(screen.availableGeometry().center())
self.move(rect.topLeft())
screen = QtGui.QGuiApplication.screenAt(QtGui.QCursor.pos())
if not screen:
screen = QtGui.QGuiApplication.primaryScreen()
# ------------------------------------------------------------------
# Button handlers
# ------------------------------------------------------------------
screen_geometry = screen.availableGeometry()
@staticmethod
def _on_connexion():
pass
# On utilise frameGeometry() de la fenêtre elle-même
window_rect = self.frameGeometry()
window_rect.moveCenter(screen_geometry.center())
@staticmethod
def _on_discord():
webbrowser.open(URLS["discord"])
self.move(window_rect.topLeft())
def _on_intranet(self):
webbrowser.open(URLS["intranet"])
self._glow.start()
# ------------------------------------------------------------------
# Mouse events → délégués au WindowDragger
# ------------------------------------------------------------------
# Mouse press event to start dragging
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
@@ -257,23 +296,25 @@ class MainWindow(QMainWindow):
self._drag_pos = None
super().mouseReleaseEvent(event)
# ------------------------------------------------------------------
# Close
# ------------------------------------------------------------------
def closeEvent(self, event):
self.config.save()
super().closeEvent(event)
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
app = QApplication(sys.argv)
# Set the stylesheet of the application
with open(f"{bundle_dir}/styles/styles.qss", 'r') as f:
style = f.read()
app.setStyleSheet(f.read())
# Load and set the global font
custom_font = QFont(load_custom_font(), 16)
if custom_font:
app.setFont(custom_font)
# Set the stylesheet of the application
app.setStyleSheet(style)
app.setFont(QFont(load_custom_font(), 16))
window = MainWindow()