"""Composants UI stylés de la GUI V6 (G4), alignés sur la maquette. Chaque helper reçoit une ``palette`` (cf. theme.py) et applique les couleurs correspondantes à des widgets customtkinter. Aucune logique métier ici. Les widgets ne sont créés qu'à l'appel (import sûr pour ``--self-test``). """ from __future__ import annotations from typing import Optional import customtkinter as ctk CARD_RADIUS = 8 def font(size: int = 13, weight: str = "normal") -> "ctk.CTkFont": return ctk.CTkFont(size=size, weight=weight) class Card(ctk.CTkFrame): """Carte maquette : fond `card`, bordure `card_border`, titre uppercase optionnel.""" def __init__(self, master, palette: dict, title: Optional[str] = None, **kwargs): super().__init__( master, fg_color=palette["card"], border_color=palette["card_border"], border_width=1, corner_radius=CARD_RADIUS, **kwargs, ) self._palette = palette self.body = self # alias pour clarté if title: ctk.CTkLabel( self, text=title.upper(), text_color=palette["text_dim"], font=font(11, "bold"), anchor="w", ).pack(anchor="w", padx=16, pady=(14, 8)) def primary_button(master, palette: dict, text: str, command=None, large: bool = False): return ctk.CTkButton( master, text=text, command=command, fg_color=palette["primary"], hover_color=palette["primary_dim"], text_color="#ffffff", corner_radius=CARD_RADIUS, height=38 if large else 32, font=font(14 if large else 13, "bold"), ) def secondary_button(master, palette: dict, text: str, command=None): return ctk.CTkButton( master, text=text, command=command, fg_color=palette["btn_sec_bg"], hover_color=palette["card_border"], text_color=palette["text"], border_color=palette["btn_sec_border"], border_width=1, corner_radius=CARD_RADIUS, height=32, font=font(13), ) def success_button(master, palette: dict, text: str, command=None): return ctk.CTkButton( master, text=text, command=command, fg_color=palette["success"], hover_color=palette["success"], text_color="#ffffff", corner_radius=CARD_RADIUS, height=32, font=font(13, "bold"), ) def pill_button(master, palette: dict, text: str, command=None, active: bool = False): """Bouton pilule (sélecteur de thème / sous-onglet).""" return ctk.CTkButton( master, text=text, command=command, fg_color=palette["primary"] if active else "transparent", hover_color=palette["primary_dim"], text_color="#ffffff" if active else palette["text_dim"], border_color=palette["card_border"] if not active else palette["primary"], border_width=2, corner_radius=99, height=30, font=font(12, "bold" if active else "normal"), ) class StatCard(ctk.CTkFrame): """Carte statistique (rgrid/sc) : grande valeur + label.""" def __init__(self, master, palette: dict, value: str, label: str, value_color: Optional[str] = None, **kwargs): super().__init__( master, fg_color=palette["btn_sec_bg"], border_color=palette["btn_sec_border"], border_width=1, corner_radius=CARD_RADIUS, **kwargs, ) ctk.CTkLabel( self, text=value, text_color=value_color or palette["primary"], font=font(22, "bold") ).pack(pady=(10, 0)) ctk.CTkLabel(self, text=label, text_color=palette["text_muted"], font=font(11)).pack( pady=(0, 10) ) class ToggleRow(ctk.CTkFrame): """Ligne de réglage (srow) : libellé + indice + interrupteur.""" def __init__(self, master, palette: dict, label: str, hint: str = "", value: bool = True, command=None, **kwargs): super().__init__(master, fg_color="transparent", **kwargs) left = ctk.CTkFrame(self, fg_color="transparent") left.pack(side="left", fill="x", expand=True) ctk.CTkLabel(left, text=label, text_color=palette["text"], font=font(13), anchor="w").pack(anchor="w") if hint: ctk.CTkLabel(left, text=hint, text_color=palette["text_muted"], font=font(11), anchor="w").pack(anchor="w") self.var = ctk.BooleanVar(value=value) self.switch = ctk.CTkSwitch( self, text="", variable=self.var, command=command, progress_color=palette["primary"], width=44, ) self.switch.pack(side="right", padx=(8, 0)) def get(self) -> bool: return bool(self.var.get())