diff --git a/src/ui/screens/home_screen.py b/src/ui/screens/home_screen.py index 1cf173b..e16975b 100644 --- a/src/ui/screens/home_screen.py +++ b/src/ui/screens/home_screen.py @@ -9,69 +9,11 @@ from PyQt6.QtGui import QFont, QPixmap, QImage, QMouseEvent from services.usb_watcher import UsbWatcher from ui.widgets.config_dialog import load_config, ConfigDialog - -BTN_STYLE = """ - QPushButton {{ - background-color: {bg}; - color: white; - border-radius: 15px; - padding: 20px; - }} - QPushButton:pressed {{ - background-color: {pressed}; - }} -""" -BTN_BLUE = BTN_STYLE.format(bg="#2196F3", pressed="#1976D2") -BTN_GREEN = BTN_STYLE.format(bg="#4CAF50", pressed="#388E3C") - -BACK_BTN_STYLE = """ - QPushButton { - background-color: #757575; - color: white; - border-radius: 12px; - padding: 12px 30px; - font-size: 16px; - } - QPushButton:pressed { background-color: #616161; } -""" - -HIGHLIGHT_STYLE = """ - QLabel { - background-color: #E3F2FD; - border: 2px solid #2196F3; - border-radius: 10px; - padding: 15px; - color: #1565C0; - font-size: 20px; - } -""" - -ERROR_STYLE = """ - QLabel { - background-color: #FFEBEE; - border: 2px solid #F44336; - border-radius: 10px; - padding: 15px; - color: #C62828; - } -""" - -SUCCESS_STYLE = """ - QLabel { - background-color: #E8F5E9; - border: 2px solid #4CAF50; - border-radius: 10px; - padding: 15px; - color: #2E7D32; - } -""" - -INFO_STYLE = """ - QLabel { - color: #333; - padding: 10px; - } -""" +from ui.style import ( + scale, scaled_font, Sizes, + blue_btn, green_btn, back_btn_style, + highlight_box, error_box, success_box +) class LongPressLabel(QLabel): @@ -80,7 +22,6 @@ class LongPressLabel(QLabel): def __init__(self, text, callback, parent=None): super().__init__(text, parent) self._callback = callback - self._press_timer = QElapsedTimer() self._long_press_timer = QTimer(self) self._long_press_timer.setSingleShot(True) self._long_press_timer.timeout.connect(self._on_long_press) @@ -109,12 +50,11 @@ class HomeScreen(QWidget): def _setup_ui(self): layout = QVBoxLayout(self) - layout.setContentsMargins(20, 15, 20, 15) - layout.setSpacing(10) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) # Titre (appui long 5s = config) self.title = LongPressLabel("Photostation", self._open_config) - self.title.setFont(QFont("", 42, QFont.Weight.Bold)) self.title.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(self.title) @@ -129,16 +69,58 @@ class HomeScreen(QWidget): self.stack.addWidget(self._build_qr_page()) # 3 self.stack.addWidget(self._build_email_page()) # 4 + def showEvent(self, event): + """Applique les styles responsive une fois la fenêtre dimensionnée.""" + super().showEvent(event) + QTimer.singleShot(0, self._apply_responsive_styles) + + def resizeEvent(self, event): + super().resizeEvent(event) + self._apply_responsive_styles() + + def _apply_responsive_styles(self): + """Recalcule toutes les tailles proportionnellement à la fenêtre.""" + m = scale(self, Sizes.MARGIN) + sp = scale(self, Sizes.SPACING) + self.layout().setContentsMargins(m, m, m, m) + self.layout().setSpacing(sp) + + # Titre + self.title.setFont(scaled_font(self, Sizes.TITLE, bold=True)) + + # Boutons de mode + btn_h = scale(self, Sizes.BTN_MODE_H) + for btn in self.mode_buttons.values(): + btn.setMinimumHeight(btn_h) + btn.setStyleSheet(blue_btn(self)) + btn.setFont(scaled_font(self, Sizes.BUTTON_LARGE)) + + # USB bouton vert si clé détectée + if self._usb_photos: + self.mode_buttons["USB"].setStyleSheet(green_btn(self)) + + # Tous les labels et boutons des sous-pages + self._style_usb_page() + self._style_wifi_page() + self._style_qr_page() + self._style_email_page() + + # Hint config + self.config_hint.setFont(scaled_font(self, Sizes.HINT)) + def _make_back_button(self): btn = QPushButton("< Retour") - btn.setFont(QFont("", 16, QFont.Weight.Bold)) - btn.setMinimumSize(150, 55) - btn.setStyleSheet(BACK_BTN_STYLE) btn.clicked.connect(self._show_modes) + self._back_buttons.append(btn) return btn + def _style_back_buttons(self): + for btn in self._back_buttons: + btn.setFont(scaled_font(self, Sizes.BUTTON_TEXT, bold=True)) + btn.setMinimumHeight(scale(self, Sizes.BTN_HEIGHT)) + btn.setStyleSheet(back_btn_style(self)) + def _make_page_header(self): - """Header commun : gros bouton Retour en haut à gauche.""" header = QHBoxLayout() header.addWidget(self._make_back_button()) header.addStretch() @@ -147,18 +129,17 @@ class HomeScreen(QWidget): # ─── Page 0 : Sélection des modes ─── def _build_mode_selection(self): + self._back_buttons = [] + page = QWidget() layout = QVBoxLayout(page) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.setSpacing(30) - subtitle = QLabel("Comment souhaitez-vous envoyer vos photos ?") - subtitle.setFont(QFont("", 18)) - subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(subtitle) + self.mode_subtitle = QLabel("Comment souhaitez-vous envoyer vos photos ?") + self.mode_subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.mode_subtitle) buttons_layout = QHBoxLayout() - buttons_layout.setSpacing(20) modes = [ ("USB", "Clé USB", self._show_usb), @@ -170,21 +151,16 @@ class HomeScreen(QWidget): self.mode_buttons = {} for label, desc, callback in modes: btn = QPushButton(f"{label}\n\n{desc}") - btn.setFont(QFont("", 16)) - btn.setMinimumSize(200, 150) - btn.setStyleSheet(BTN_BLUE) btn.clicked.connect(callback) buttons_layout.addWidget(btn) self.mode_buttons[label] = btn layout.addLayout(buttons_layout) - # Hint config - hint = QLabel("Appui long sur le titre pour accéder à la configuration") - hint.setFont(QFont("", 10)) - hint.setAlignment(Qt.AlignmentFlag.AlignCenter) - hint.setStyleSheet("color: #bbb;") - layout.addWidget(hint) + self.config_hint = QLabel("Appui long sur le titre pour accéder à la configuration") + self.config_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.config_hint.setStyleSheet("color: #bbb;") + layout.addWidget(self.config_hint) return page @@ -193,28 +169,19 @@ class HomeScreen(QWidget): def _build_usb_page(self): page = QWidget() layout = QVBoxLayout(page) - layout.setSpacing(20) - layout.addLayout(self._make_page_header()) - instruction = QLabel("Insérez votre clé USB dans le port\nde la borne") - instruction.setFont(QFont("", 24)) - instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(instruction) + self.usb_instruction = QLabel("Insérez votre clé USB\ndans le port de la borne") + self.usb_instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.usb_instruction) layout.addStretch() self.usb_status_label = QLabel("Clé USB non détectée\nInsérez votre clé USB puis patientez quelques secondes") - self.usb_status_label.setFont(QFont("", 18)) self.usb_status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.usb_status_label.setMinimumHeight(80) - self.usb_status_label.setStyleSheet(ERROR_STYLE) layout.addWidget(self.usb_status_label) self.usb_continue_btn = QPushButton("Continuer") - self.usb_continue_btn.setFont(QFont("", 18, QFont.Weight.Bold)) - self.usb_continue_btn.setMinimumSize(250, 60) - self.usb_continue_btn.setStyleSheet(BTN_GREEN) self.usb_continue_btn.clicked.connect(self._import_usb) self.usb_continue_btn.hide() layout.addWidget(self.usb_continue_btn, alignment=Qt.AlignmentFlag.AlignCenter) @@ -222,127 +189,138 @@ class HomeScreen(QWidget): layout.addStretch() return page + def _style_usb_page(self): + self.usb_instruction.setFont(scaled_font(self, Sizes.HEADING)) + self.usb_status_label.setFont(scaled_font(self, Sizes.BODY)) + self._update_usb_status() + self.usb_continue_btn.setFont(scaled_font(self, Sizes.BUTTON_LARGE, bold=True)) + self.usb_continue_btn.setMinimumHeight(scale(self, Sizes.BTN_HEIGHT)) + self.usb_continue_btn.setMinimumWidth(scale(self, 30)) + self.usb_continue_btn.setStyleSheet(green_btn(self)) + self._style_back_buttons() + # ─── Page 2 : WiFi ─── def _build_wifi_page(self): page = QWidget() layout = QVBoxLayout(page) - layout.setSpacing(20) - layout.addLayout(self._make_page_header()) - instruction = QLabel("Connectez-vous au réseau WiFi\nde la borne depuis votre téléphone") - instruction.setFont(QFont("", 24)) - instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(instruction) + self.wifi_instruction = QLabel("Connectez-vous au réseau WiFi\nde la borne depuis votre téléphone") + self.wifi_instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.wifi_instruction) layout.addStretch() - self.wifi_name_label = QLabel(f"Nom du réseau : {self.config['wifi_ap_name']}") - self.wifi_name_label.setFont(QFont("", 22, QFont.Weight.Bold)) + self.wifi_name_label = QLabel() self.wifi_name_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.wifi_name_label.setStyleSheet(HIGHLIGHT_STYLE) layout.addWidget(self.wifi_name_label) - self.wifi_pass_label = QLabel(f"Mot de passe : {self.config['wifi_ap_password']}") - self.wifi_pass_label.setFont(QFont("", 22, QFont.Weight.Bold)) + self.wifi_pass_label = QLabel() self.wifi_pass_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.wifi_pass_label.setStyleSheet(HIGHLIGHT_STYLE) layout.addWidget(self.wifi_pass_label) - step2 = QLabel("Une fois connecté, une page s'ouvrira\nautomatiquement pour envoyer vos photos") - step2.setFont(QFont("", 16)) - step2.setAlignment(Qt.AlignmentFlag.AlignCenter) - step2.setStyleSheet("color: #666;") - layout.addWidget(step2) + self.wifi_hint = QLabel("Une fois connecté, une page s'ouvrira\nautomatiquement pour envoyer vos photos") + self.wifi_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.wifi_hint.setStyleSheet("color: #666;") + layout.addWidget(self.wifi_hint) self.wifi_status_label = QLabel("En attente de connexion...") - self.wifi_status_label.setFont(QFont("", 16)) self.wifi_status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.wifi_status_label.setStyleSheet(INFO_STYLE) layout.addWidget(self.wifi_status_label) layout.addStretch() return page + def _style_wifi_page(self): + self.wifi_instruction.setFont(scaled_font(self, Sizes.HEADING)) + self.wifi_name_label.setFont(scaled_font(self, Sizes.BODY_LARGE, bold=True)) + self.wifi_name_label.setStyleSheet(highlight_box(self)) + self.wifi_pass_label.setFont(scaled_font(self, Sizes.BODY_LARGE, bold=True)) + self.wifi_pass_label.setStyleSheet(highlight_box(self)) + self.wifi_hint.setFont(scaled_font(self, Sizes.BODY)) + self.wifi_status_label.setFont(scaled_font(self, Sizes.BODY)) + self._refresh_config_labels() + # ─── Page 3 : QR Code ─── def _build_qr_page(self): page = QWidget() layout = QVBoxLayout(page) - layout.setSpacing(15) - layout.addLayout(self._make_page_header()) - instruction = QLabel("Scannez ce QR code avec votre téléphone\npour envoyer vos photos") - instruction.setFont(QFont("", 24)) - instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(instruction) + self.qr_instruction = QLabel("Scannez ce QR code avec votre téléphone\npour envoyer vos photos") + self.qr_instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.qr_instruction) self.qr_image_label = QLabel() - self.qr_image_label.setFixedSize(250, 250) self.qr_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.qr_image_label.setStyleSheet("background-color: white; border: 2px solid #ccc; border-radius: 10px;") layout.addWidget(self.qr_image_label, alignment=Qt.AlignmentFlag.AlignCenter) self.qr_url_label = QLabel("") - self.qr_url_label.setFont(QFont("", 14)) self.qr_url_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.qr_url_label.setStyleSheet("color: #666;") layout.addWidget(self.qr_url_label) - hint = QLabel("Envoyez vos photos depuis la page web\nqui s'ouvrira sur votre téléphone") - hint.setFont(QFont("", 16)) - hint.setAlignment(Qt.AlignmentFlag.AlignCenter) - hint.setStyleSheet("color: #666;") - layout.addWidget(hint) + self.qr_hint = QLabel("Envoyez vos photos depuis la page web\nqui s'ouvrira sur votre téléphone") + self.qr_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.qr_hint.setStyleSheet("color: #666;") + layout.addWidget(self.qr_hint) self.qr_status_label = QLabel("En attente de photos...") - self.qr_status_label.setFont(QFont("", 16)) self.qr_status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.qr_status_label.setStyleSheet(INFO_STYLE) layout.addWidget(self.qr_status_label) layout.addStretch() return page + def _style_qr_page(self): + self.qr_instruction.setFont(scaled_font(self, Sizes.HEADING)) + qr_size = scale(self, 35) # QR = 35% de la hauteur + self.qr_image_label.setFixedSize(qr_size, qr_size) + self.qr_url_label.setFont(scaled_font(self, Sizes.BODY)) + self.qr_hint.setFont(scaled_font(self, Sizes.BODY)) + self.qr_status_label.setFont(scaled_font(self, Sizes.BODY)) + # ─── Page 4 : Email ─── def _build_email_page(self): page = QWidget() layout = QVBoxLayout(page) - layout.setSpacing(20) - layout.addLayout(self._make_page_header()) - instruction = QLabel("Envoyez vos photos par email\nà l'adresse suivante :") - instruction.setFont(QFont("", 24)) - instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(instruction) + self.email_instruction = QLabel("Envoyez vos photos par email\nà l'adresse suivante :") + self.email_instruction.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.email_instruction) layout.addStretch() - self.email_address_label = QLabel(self.config["email_address"]) - self.email_address_label.setFont(QFont("", 28, QFont.Weight.Bold)) + self.email_address_label = QLabel() self.email_address_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.email_address_label.setStyleSheet(HIGHLIGHT_STYLE) + self.email_address_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) layout.addWidget(self.email_address_label) - hint = QLabel("Joignez vos photos en pièce jointe\net envoyez depuis votre téléphone") - hint.setFont(QFont("", 16)) - hint.setAlignment(Qt.AlignmentFlag.AlignCenter) - hint.setStyleSheet("color: #666;") - layout.addWidget(hint) + self.email_hint = QLabel("Joignez vos photos en pièce jointe\net envoyez depuis votre téléphone") + self.email_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.email_hint.setStyleSheet("color: #666;") + layout.addWidget(self.email_hint) self.email_status_label = QLabel("En attente de réception...") - self.email_status_label.setFont(QFont("", 16)) self.email_status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.email_status_label.setStyleSheet(INFO_STYLE) layout.addWidget(self.email_status_label) layout.addStretch() return page + def _style_email_page(self): + self.email_instruction.setFont(scaled_font(self, Sizes.HEADING)) + self.email_address_label.setFont(scaled_font(self, 4.5, bold=True)) + self.email_address_label.setStyleSheet(highlight_box(self)) + self.email_hint.setFont(scaled_font(self, Sizes.BODY)) + self.email_status_label.setFont(scaled_font(self, Sizes.BODY)) + self._refresh_config_labels() + # ─── Navigation ─── def _show_modes(self): @@ -373,7 +351,6 @@ class HomeScreen(QWidget): self._refresh_config_labels() def _refresh_config_labels(self): - """Met à jour les labels avec la config actuelle.""" self.config = load_config() self.wifi_name_label.setText(f"Nom du réseau : {self.config['wifi_ap_name']}") self.wifi_pass_label.setText(f"Mot de passe : {self.config['wifi_ap_password']}") @@ -390,14 +367,14 @@ class HomeScreen(QWidget): def _on_usb_inserted(self, mount_path, photos): self._usb_mount = mount_path self._usb_photos = photos - self.mode_buttons["USB"].setStyleSheet(BTN_GREEN) + self.mode_buttons["USB"].setStyleSheet(green_btn(self)) self._update_usb_status() def _on_usb_removed(self, mount_path): if mount_path == self._usb_mount: self._usb_mount = None self._usb_photos = [] - self.mode_buttons["USB"].setStyleSheet(BTN_BLUE) + self.mode_buttons["USB"].setStyleSheet(blue_btn(self)) self._update_usb_status() def _update_usb_status(self): @@ -406,11 +383,11 @@ class HomeScreen(QWidget): self.usb_status_label.setText( f"Clé USB détectée — {count} photo{'s' if count != 1 else ''} trouvée{'s' if count != 1 else ''}" ) - self.usb_status_label.setStyleSheet(SUCCESS_STYLE) + self.usb_status_label.setStyleSheet(success_box(self)) self.usb_continue_btn.show() else: self.usb_status_label.setText("Clé USB non détectée\nInsérez votre clé USB puis patientez quelques secondes") - self.usb_status_label.setStyleSheet(ERROR_STYLE) + self.usb_status_label.setStyleSheet(error_box(self)) self.usb_continue_btn.hide() def _import_usb(self): @@ -438,8 +415,10 @@ class HomeScreen(QWidget): qimage.loadFromData(buffer.read()) pixmap = QPixmap.fromImage(qimage) + qr_size = scale(self, 35) self.qr_image_label.setPixmap( - pixmap.scaled(230, 230, Qt.AspectRatioMode.KeepAspectRatio, + pixmap.scaled(qr_size - 20, qr_size - 20, + Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) ) self.qr_url_label.setText(upload_url) diff --git a/src/ui/style.py b/src/ui/style.py new file mode 100644 index 0000000..8bfc05c --- /dev/null +++ b/src/ui/style.py @@ -0,0 +1,128 @@ +"""Styles et tailles responsive — tout est relatif à la taille de la fenêtre.""" + +from PyQt6.QtWidgets import QWidget +from PyQt6.QtGui import QFont + + +def scale(widget: QWidget, base_pct: float) -> int: + """Retourne une valeur en pixels proportionnelle à la hauteur de la fenêtre. + base_pct = pourcentage de la hauteur (ex: 5.0 = 5% de la hauteur).""" + window = widget.window() + h = window.height() if window else 768 + return max(1, int(h * base_pct / 100)) + + +def scaled_font(widget: QWidget, pct: float, bold: bool = False) -> QFont: + """Crée une QFont dont la taille est proportionnelle à la fenêtre.""" + size = scale(widget, pct) + weight = QFont.Weight.Bold if bold else QFont.Weight.Normal + return QFont("", size, weight) + + +# Pourcentages de référence (basés sur hauteur fenêtre) +class Sizes: + TITLE = 7.0 # titre principal + SUBTITLE = 3.0 # sous-titre + HEADING = 4.0 # titres de page + BODY = 2.8 # texte courant + BODY_LARGE = 3.5 # texte mis en évidence + BUTTON_TEXT = 2.5 # texte des boutons + BUTTON_LARGE = 3.0 # gros boutons + HINT = 1.8 # texte discret + SPACING = 2.5 # espacement standard + MARGIN = 2.5 # marges + BTN_HEIGHT = 8.0 # hauteur bouton standard + BTN_WIDTH = 22.0 # largeur bouton mode (% largeur) + BTN_MODE_H = 20.0 # hauteur bouton mode + RADIUS = 2.0 # border-radius + PADDING = 2.5 # padding boutons + + +def btn_style(widget: QWidget, bg: str, pressed: str) -> str: + r = scale(widget, Sizes.RADIUS) + p = scale(widget, Sizes.PADDING) + fs = scale(widget, Sizes.BUTTON_TEXT) + return f""" + QPushButton {{ + background-color: {bg}; + color: white; + border-radius: {r}px; + padding: {p}px; + font-size: {fs}px; + }} + QPushButton:pressed {{ + background-color: {pressed}; + }} + """ + + +def blue_btn(widget: QWidget) -> str: + return btn_style(widget, "#2196F3", "#1976D2") + + +def green_btn(widget: QWidget) -> str: + return btn_style(widget, "#4CAF50", "#388E3C") + + +def back_btn_style(widget: QWidget) -> str: + r = scale(widget, Sizes.RADIUS) + p = scale(widget, Sizes.PADDING) + fs = scale(widget, Sizes.BUTTON_TEXT) + return f""" + QPushButton {{ + background-color: #757575; + color: white; + border-radius: {r}px; + padding: {p}px {int(p*1.5)}px; + font-size: {fs}px; + }} + QPushButton:pressed {{ background-color: #616161; }} + """ + + +def highlight_box(widget: QWidget) -> str: + r = scale(widget, Sizes.RADIUS) + p = scale(widget, Sizes.PADDING) + fs = scale(widget, Sizes.BODY_LARGE) + return f""" + QLabel {{ + background-color: #E3F2FD; + border: 2px solid #2196F3; + border-radius: {r}px; + padding: {p}px; + color: #1565C0; + font-size: {fs}px; + }} + """ + + +def error_box(widget: QWidget) -> str: + r = scale(widget, Sizes.RADIUS) + p = scale(widget, Sizes.PADDING) + fs = scale(widget, Sizes.BODY) + return f""" + QLabel {{ + background-color: #FFEBEE; + border: 2px solid #F44336; + border-radius: {r}px; + padding: {p}px; + color: #C62828; + font-size: {fs}px; + }} + """ + + +def success_box(widget: QWidget) -> str: + r = scale(widget, Sizes.RADIUS) + p = scale(widget, Sizes.PADDING) + fs = scale(widget, Sizes.BODY) + return f""" + QLabel {{ + background-color: #E8F5E9; + border: 2px solid #4CAF50; + border-radius: {r}px; + padding: {p}px; + color: #2E7D32; + font-size: {fs}px; + }} + """