Communitygithub.com

mawwki/pyqt6-md3-ui

Créez, reconcevez ou restylez une application GUI desktop avec PyQt6/PySide6 pour qu'elle ressemble à une application Material Design 3 (MD3) polie (fenêtre sans cadre, boutons de fenêtre Material, panneau de navigation avec indicateur en pilule, cartes surélevées, boutons tonaux/remplis avec ondulation, interrupteurs/curseurs/cases à cocher/boutons segmentés MD3, thèmes clair/sombre).

Qu'est-ce que pyqt6-md3-ui ?

Ce skill spécifie comment construire une application de bureau complète avec PyQt6 qui imite fidèlement l'esthétique de Material Design 3 (MD3) de Google. Contrairement à l'utilisation du style natif de Qt, ce skill implémente manuellement les composants visuels clés de MD3 : une fenêtre sans cadre (frameless) avec ses propres boutons de fenêtre, un tiroir de navigation avec des icônes et un indicateur en pilule, des cartes Material Elevation, des boutons remplis et tonaux avec des effets d'ondulation (ripple) et des calques d'état (state layers), ainsi que des interrupteurs, curseurs, cases à cocher et boutons segmentés stylisés MD3. Il fournit la spécification exacte d'une application de référence (`assets/app_template.py`), y compris un système de thème pour les modes clair et sombre, des animations fluides via `QPropertyAnimation`, et une gestion native du redimensionnement et du déplacement de la fenêtre en utilisant `QWindow.startSystemResize`/`startSystemMove`. L'agent doit soit copier et adapter l'application de référence, soit la reconstruire à partir de zéro en suivant cette documentation détaillée.

Compatible avec~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/mawwki/pyqt6-ui-skills/tree/HEAD/pyqt6-md3-ui

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

PyQt6 en style Material Design 3 — spécification de l'application de référence

Ci-dessous se trouve la spécification complète pour reproduire assets/app_template.py comportement par comportement. Modélise l'agent designer d'interfaces d'un studio qui crée des applications MD3 de bureau : palette de rôles (primary / on-primary / containers / surface / outline), calques d'état (state layers), ondulation (ripple), arrondis selon l'échelle des formes, animations exactement là où elles expliquent ce qui s'est passé — et pas plus.

Règle principale : « style MD3 » dans Qt n'est pas une API native, mais une esthétique recréée manuellement : fenêtre sans cadre + QSS personnalisé + QPropertyAnimation pour la fluidité. Ainsi l'application a la même apparence sur Windows, Linux et macOS. Le style Qt natif ne permet pas les arrondis, la transparence, les calques d'état et les animations.

La réalisation de référence est assets/app_template.py (un seul fichier). La tâche de l'agent : soit copier et l'adapter, soit le reconstruire à partir de zéro exactement selon ce document.


1. Démarrage rapide

pip install PyQt6
python app_template.py

Construction pour l'utilisateur : pyinstaller --windowed --icon=app.ico app_template.py.

L'application se lance ainsi :

def main() -> None:
    app = QApplication(sys.argv)
    app.setStyle("Fusion")                       # seul style respectant QSS partout
    app.setFont(system_font(14, QFont.Weight.Normal))
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

2. Structure du fichier app_template.py (l'ordre est important pour la lisibilité)

  1. Imports : math, sys, time, dataclasses, pathlib.Path ; QtCore (Qt, QEvent, QPropertyAnimation, QEasingCurve, QRect(F), QPointF, QParallelAnimationGroup, QVariantAnimation, QTimer, pyqtProperty, pyqtSignal) ; QtGui (QColor, QFont, QPainter, QPainterPath, QFontDatabase, QFontMetricsF, QPen, QPixmap, QIcon, QPalette) ; QtSvg (QSvgRenderer) ; QtWidgets (tous les widgets nécessaires).
  2. Jetons de design : Theme (dataclass), LIGHT/DARK, constantes de rayons/durées, REPLIES, SVG_EYE/SVG_EYE_SLASH, PAGES.
  3. Helpers : system_font, rgba, accent_fg, shadow_color, lerp_color, animate_property, draw_checkmark.
  4. Widgets personnalisés dans l'ordre des dépendances : MdButton, MdSwitch, MdSlider, MdCheckBox, MdSegmentButton/MdSegmentedControl, DropGlyph/FileDropArea, WindowButton, SidebarToggle, TopAppBar, NavButton/NavigationDrawer.
  5. build_stylesheet(theme).
  6. MainWindow + main().

3. Jetons de design (valeurs exactes, rôles MD3)

Theme — dataclass figée (frozen), toutes les couleurs sont des chaînes hexadécimales, rôles MD3 : primary, on_primary, primary_container, on_primary_container, secondary_container, on_secondary_container, surface, surface_container_low, surface_container, surface_container_high, surface_container_highest, on_surface, on_surface_variant, outline, outline_variant, error, on_error, error_container, on_error_container, shadow_alpha (int 0..255, l'ombre est toujours noire).

LIGHT = Theme(primary="#6750A4", on_primary="#FFFFFF",
              primary_container="#EADDFF", on_primary_container="#21005D",
              secondary_container="#E8DEF8", on_secondary_container="#1D192B",
              surface="#FEF7FF", surface_container_low="#F7F2FA",
              surface_container="#F3EDF7", surface_container_high="#ECE6F0",
              surface_container_highest="#E6E0E9", on_surface="#1D1B20",
              on_surface_variant="#49454F", outline="#79747E", outline_variant="#CAC4D0",
              error="#B3261E", on_error="#FFFFFF",
              error_container="#F9DEDC", on_error_container="#410E0B", shadow_alpha=45)
DARK  = Theme(primary="#D0BCFF", on_primary="#381E72",
              primary_container="#4F378B", on_primary_container="#EADDFF",
              secondary_container="#4A4458", on_secondary_container="#E8DEF8",
              surface="#141218", surface_container_low="#1D1B20",
              surface_container="#211F26", surface_container_high="#2B2930",
              surface_container_highest="#36343B", on_surface="#E6E0E9",
              on_surface_variant="#CAC4D0", outline="#938F99", outline_variant="#49454F",
              error="#F2B8B5", on_error="#601410",
              error_container="#8C1D18", on_error_container="#F9DEDC", shadow_alpha=120)

Les paires « rôle / on-rôle » sont obligatoires : le texte au-dessus de primary — toujours on_primary, au-dessus de secondary_container — on_secondary_container, au-dessus de primary_container — on_primary_container. Pas de code en dur « white »/« black ». accent_fg(theme) est conservé comme helper de compatibilité et retourne theme.on_primary.

def rgba(hex_color: str, alpha_0_255: int) -> str:   # QSS attend un alpha 0..255, pas 0..1
    c = QColor(hex_color)
    return f"rgba({c.red()}, {c.green()}, {c.blue()}, {alpha_0_255})"

def shadow_color(theme: Theme) -> QColor:
    c = QColor(0, 0, 0); c.setAlpha(theme.shadow_alpha); return c

Constantes :

  • Formes : RADIUS_WINDOW = 16, RADIUS_CARD = 12, RADIUS_MENU = 8, RADIUS_FIELD = 4, SIDEBAR_WIDTH = 220. Boutons/pilules — radius = hauteur / 2 ; chips/segments — full.
  • EDGE_MARGIN = 12 (correspond à la marge de la racine sous l'ombre), masques de bits des bords EDGE_TOP, EDGE_BOTTOM, EDGE_LEFT, EDGE_RIGHT = 1, 2, 4, 8.
  • DUR_FAST = 140 (hover), DUR_PRESS = 90, DUR_RELEASE = 200, DUR_MED = 250 (pages, tiroir, interrupteur, segments), DUR_RIPPLE = 450 ; EASE = OutCubic, EASE_PRESS = OutQuad.
  • PAGES = ["Vue d'ensemble", "Tâches", "Saisie", "Chat", "Fichiers", "Paramètres"].
  • REPLIES — 5 réponses courtes du bot.
  • SVG_EYE / SVG_EYE_SLASH — Symboles Material visibility / visibility_off (viewBox 0 0 24 24, fill="currentColor").

Typographie : system_font(point_size=14, weight) — chaîne Roboto → Google Sans → Noto Sans → Segoe UI, setStyleStrategy(PreferAntialias). Tailles : titre de page 24, titre de carte 16/Medium, texte de bouton 14/Medium, body 14, secondaire 12, heure 11. Marges selon l'échelle 4/8/12/16/24/32.

Ombres : fenêtre/panneau — blurRadius 24, offset (0,4) ; cartes (élévation 1) — blurRadius 8, offset (0,1) ; couleur shadow_color(theme).

Calques d'état (au-dessus du conteneur, couleur du « on-rôle ») : hover 0.08, press 0.12 (c'est-à-dire 0.08*hover + 0.04*press) ; pour le curseur hover 0.10 / press 0.16 ; pour la case à cocher 0.06 (non cochée) / 0.10 (cochée).


4. Squelette de la fenêtre

self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setMinimumSize(760, 480)
self.resize(960, 600)
self.setMouseTracking(True)
QApplication.instance().installEventFilter(self)   # pour le redimensionnement par les bords

Arborescence (_build_ui) : QMainWindow → root (QWidget, margins 12) → panel (QFrame#Panel, drop-shadow) → TopAppBar (56px) + body (QHBoxLayout) → NavigationDrawer (220px) + separator (1px, outline_variant) + QStackedWidget. Le panneau reçoit build_stylesheet(theme) — QSS est hérité vers le bas. En complément, _apply_palette(theme) définit les rôles QPalette (Window/Base=surface, Text=on_surface, Highlight=primary, HighlightedText=on_primary, PlaceholderText=on_surface_variant, ToolTipBase=surface_container_high…) pour les parties dessinées nativement.

L'ombre et l'arrondi vivent sur le QFrame#Panel enfant, et non sur la fenêtre elle-même : la fenêtre est transparente et laisse une marge de 12px pour l'ombre. Il ne faut pas appliquer l'effet sur QMainWindow.

Déplacement et redimensionnement

Déplacer la fenêtre — mécanisme natif Qt6 (dans TopAppBar.mousePressEvent) :

handle = self._window.windowHandle()
if handle is not None:
    handle.startSystemMove()

Double-clic sur TopAppBar — maximise/normalise.

Redimensionner par les bords — QWindow.startSystemResize natif. Dans MainWindow, un filtre d'événements sur QApplication est installé : sur MouseButtonPress(Gauche) dans la zone ≤ EDGE_MARGIN appelle _start_system_resize(edges) et retourne True ; sur MouseMove sans bouton met à jour le curseur ; sur Leave réinitialise le curseur.

_resize_edges_for(pos) → masque de bits des bords (uniquement si la fenêtre est visible et non maximisée). _cursor_for_edges : centre → Arrow ; gauche/droite → SizeHor ; haut/bas → SizeVer ; haut+gauche ou bas+droite → SizeFDiag ; sinon → SizeBDiag.

Piège PyQt6 : Qt.Edge ne peut pas être converti en int et ne peut pas être mélangé avec int via |. Les mathématiques des bords utilisent leurs propres constantes de bits EDGE_*, le drapeau est assemblé ainsi :

flag = Qt.Edge(0)
if edges & EDGE_TOP:    flag |= Qt.Edge.TopEdge
if edges & EDGE_BOTTOM: flag |= Qt.Edge.BottomEdge
if edges & EDGE_LEFT:   flag |= Qt.Edge.LeftEdge
if edges & EDGE_RIGHT:  flag |= Qt.Edge.RightEdge
handle.startSystemResize(flag)

5. Widgets personnalisés (paramètres exacts)

MdButton (filled / tonal, calque d'état + ondulation)

  • QPushButton, hauteur 40, rayon de pilule 20, police 14/Medium, padding 0 24px, minWidth = texte + 48. kind="filled" → bg primary/fg on_primary ; kind="tonal" → bg secondary_container/fg on_secondary_container.
  • QSS : background: transparent; color: {fg} — le fond est dessiné manuellement, le texte via super().paintEvent().
  • Q_PROPERTY hover/press/ripple (float). Calque d'état de couleur fg : alphaF = 0.08*hover + 0.04*press, clip par le rectangle arrondi.
  • Ripple : mousePressEvent enregistre le point (_ripple_origin), pressed lance _ripple_anim 0→1 (DUR_RIPPLE) ; dans paint — ellipse à partir du point de rayon max_r*ripple (max_r = distance jusqu'au coin le plus éloigné + 4), alphaF = 0.18*(1-ripple), clip par la pilule. Conserver la référence de l'animation dans un attribut.

MdSwitch (interrupteur MD3)

  • QWidget 52×32, signal toggled(bool) uniquement au clic ; setChecked anime silencieusement.
  • Q_PROPERTY knobPos (0..1) DUR_MED/EASE, plus hover/press.
  • Off : piste surface_container_highest + contour 2px outline ; bouton 16px couleur outline, centre x=16. On : piste primary sans contour ; bouton 24px on_primary, centre x=36, à l'intérieur coche couleur primary (draw_checkmark, pen 2px). Press : bouton 28px.
  • Piste/bouton sont interpolés avec lerp_color selon knobPos ; le contour disparaît à t→1 (alpha = 255*(1-t)) ; cercle de calque d'état de 40px autour du bouton.

MdSlider

  • QWidget hauteur 28, valueChanged(int). Piste 4px rayon 2 : inactif surface_container_highest, actif primary. Bouton — cercle 20px primary + ombre légère (noir alpha 30, offset y+1). Calque d'état 40px primary alphaF = 0.10*hover + 0.06*press.
  • Valeur à partir de x : pad = 10 (rayon du bouton), ratio = (x-pad)/(width-2*pad) ; pendant le drag — sans inertie, sinon fill est animé DUR_FAST.

MdCheckBox

  • QWidget hauteur 24, police 14, minWidth = 34 + texte. Boîte 18×18 rayon 2 at x=3.
  • Non cochée : contour 2px on_surface_variant. Cochée : remplissage primary + coche on_primary (pen 2px RoundCap/RoundJoin), la coche est « dessinée » progressivement (check 0..1, deux lignes segmentées via drawPath).
  • Calque d'état 36px : on_surface 0.06 (non cochée) / primary 0.10 (cochée) * hover.
  • boxScale (OutBack) compresse la boîte au press ; setChecked(checked, animate) émet toggled ; clic dans mouseReleaseEvent uniquement si le curseur est à l'intérieur du rect.

MdSegmentedControl / MdSegmentButton

  • Conteneur hauteur 40, rayon de pilule 20, contour 1px outline (dessiné dans paintEvent).
  • Segments de largeur égale (addWidget(btn, 1)), hauteur 38, police 14/Medium ; margins (1,1,1,1), spacing 0. Séparateurs 1px outline entre les voisins NON sélectionnés (dessinés dans paintEvent, coordonnées entières).
  • Indicateur — QFrame#SegIndicator (bg secondary_container, radius 19) derrière les boutons, QPropertyAnimation(geometry) DUR_MED ; dans showEvent — lower() + géométrie sans animation.
  • Segment sélectionné : texte on_secondary_container + coche à gauche du texte (draw_checkmark) ; non sélectionné : on_surface. La couleur du texte est animée (color propriété QColor). Calque d'état du segment — pilule de la couleur du on-rôle.

DropGlyph / FileDropArea

  • Glyphe 38×38 : flèche vers le bas + plateau, couleur lerp(on_surface_variant, primary, level), halo primary alpha 0.10*level.
  • FileDropArea(QFrame) : minHeight 150, setAcceptDrops(True). Fond surface_container_low radius 16 ; pointillé 2px outline_variant (dashPattern [4,3.5]) ; au hover/drag — pointillé primary + overlay primary_container alpha ≈ 60glow+20press.
  • dragEnterEvent(hasUrls) / dragLeaveEvent / dropEvent (uniquement isLocalFile()) → dropped(list[str]) ; clic → QFileDialog.getOpenFileNames. Toute la logique des signaux est inchangée.

WindowButton (boutons de fenêtre)

  • QPushButton 40×40, radius 20, Q_PROPERTY hover/press, set_maximized(bool).
  • Glyphes QPen 1.8 RoundCap/RoundJoin, coordonnées entières : minimize — ligne horizontale ; maximize — carré 12×12 (contour), en mode agrandi — deux carrés superposés ; close — croix X.
  • Couleur du glyphe lerp(on_surface_variant, on_surface, hover) ; close au hover — fond error (cercle 40px) + glyphe on_error. Les autres au hover — calque d'état on_surface 0.08.
  • Ordre à droite : minimize, maximize, close.

SidebarToggle

  • QPushButton 40×40, calque d'état on_surface 0.08, glyphe lerp(on_surface_variant, on_surface, hover).
  • Glyphe : cadre 16×16 radius 2 + séparateur ; panneau ouvert — séparateur à gauche + chevron < (panel-left-close) ; réduit — séparateur à droite (panel-left-open).
  • set_collapsed(bool) ; synchronise MainWindow.toggle_sidebar.

TopAppBar

  • QWidget#TopAppBar hauteur 56, transparent par-dessus Panel ; margins (8,0,8,0), spacing 4.
  • À gauche SidebarToggle, puis titre « MonProjet » 18/Medium on_surface, stretch, à droite WindowButton minimize/maximize/close. _max_btn + _sync_max_state via eventFilter sur WindowStateChange. startSystemMove + double-clic pour maximiser.

NavButton / NavigationDrawer

  • NavButton(QPushButton#NavButton) hauteur 48, police 14, checkable ; icône 20×20 at x=16 dessinée avec QPainter (Material Symbols outlined) : dashboard (4 carrés), tasks (cercle+coche), edit (crayon), chat (bulle), folder, tune (3 lignes + boutons). Texte via QSS padding-left: 48px.
  • nextCheckState(...) — logique de sélection unique via l'attribut _group stocké dans le tiroir de navigation. L'indicateur de sélection est un QFrame#PillIndicator (hauteur 28, radius 14, fond secondary_container) positionné par QPropertyAnimation(geometry) DUR_MED. Le tiroir est un QFrame#NavDrawer de 220px de large avec surface comme fond ; en mode réduit (collapsed=True), la largeur est de 56px (juste les icônes) ; le mode réduit/rétracté est animé avec QPropertyAnimation(minimumWidth).

6. build_stylesheet(theme)

Retourne un QSS (chaîne) avec l'ensemble du thème : Panel (background: surface, border-radius: 16px), cartes, séparateur, etc. Les couleurs sont injectées via f-string, les widgets natifs sont stylisés de force (QPushButton, QLineEdit, QListWidget, QScrollBar, QSlider — déjà remplacés, mais laissés en secours). QSS ne fait que les couleurs de fond/texte ; la géométrie fine, les calques d'état, les ombres et les icônes sont dans les paintEvent.


7. MainWindow (exigences exactes)

__init__ : thème, état max, pages ; _build_ui, _apply_palette, _sync_sidebar ; _fake_theme_transition (animation de niveau de thème pour la démo). toggle_theme : LIGHT↔DARK (_current_theme + _apply_palette + build_stylesheet + touch). _build_pages : empile les pages ; _switch_page(index) : appelle stack.setCurrentIndex(idx) + met à jour la sélection du tiroir.

toggle_sidebar : animation via QPropertyAnimation(minimumWidth) de NavDrawer DUR_MED + modifie la visibilité du séparateur show()/hide(). Au clic sur SidebarToggle : change _collapsed ET appelle toggle_sidebar.

Redimensionnement par les bords : eventFilter sur QApplication (voir §4).

_fake_theme_transition : QTimer.singleShot(60, toggle_theme) — la transition « réelle » est un cycle clair↔foncé toutes les 60ms pendant 1,5 s ; pour l'utilisateur, chaque cycle est déclenché manuellement.

_switch_page(index) : appelle setCurrentIndex + NavButton.setChecked ; l'animation de transition des pages est gérée par QStackedWidget (sans animation par défaut, mais le DUR_MED peut être appliqué via QVariantAnimation sur l'opacité).


8. Comportement de l'application de référence (tests)

  1. La fenêtre a un fond transparent avec une ombre (comme sur macOS/Windows).
  2. Réduire/agrandir/fermer fonctionne ; fermeture → Qt.quit.
  3. Double-clic sur la barre de titre → maximise ; icône de maximisation change.
  4. Déplacer la fenêtre par la barre de titre fonctionne (startSystemMove).
  5. Redimensionner par tous les bords/coins (startSystemResize) ; curseurs corrects.
  6. Les pages changent via le tiroir ; l'indicateur en pilule glisse (QPropertyAnimation geometry).
  7. Le tiroir se réduit/étend (220↔56px).
  8. Les interrupteurs cliquent ; les cases à cocher se cochent/décochent ; les curseurs glissent.
  9. Le chat a des messages (bouton + 5 réponses) ; les fichiers ont le glisser-déposer et l'icône animée.
  10. Le thème clair/foncé change tout (palette + QSS + touch()). _fake_theme_transition montre le cycle (optionnellement : retiré dans la version finale).
  11. Aucune fuite de mémoire ; toutes les animations ont une référence stockée.

9. Erreurs fréquentes PyQt6 à éviter

  • QSS sur un widget parent n'est pas hérité par les enfants si ces derniers ont leur propre setStyleSheet. Utiliser des ancêtres QFrame#ID ou une feuille de style unique au niveau de l'application.
  • QTimer.singleShot crée un minuteur qui n'est pas un QObject ; il peut être détruit accidentellement. Stocker QTimer() dans self si un arrêt est nécessaire.
  • QPropertyAnimation doit être stocké ; sinon il est détruit après la première itération.
  • QPainter dans paintEvent : toujours commencer par painter.setRenderHint, ne pas oublier painter.end() (géré par le context manager).
  • QColor ne peut pas être animé directement ; utiliser des Q_PROPERTY float (r, g, b) ou une animation de QVariant avec QColor.
  • Matplotlib FigureCanvasQTAgg ne fonctionne pas avec FramelessWindowHint car QPainter natif attend un masque. Utiliser plutôt des remplacements Pixmap.

10. Organisation du code (upload unique)

app_template.py est un seul fichier (≈1800 lignes). Pour la production, diviser en modules. Le nom du fichier reste app_template.py ; l'agent ne doit PAS renommer main.py ou run.py. La signature doit permettre de copier-coller le fichier dans assets.

pip install PyQt6
python app_template.py

Construction : pyinstaller --windowed --icon=app.ico app_template.py. Le fichier .spec doit inclure les icônes supplémentaires et les assets.


11. Limites et pièges

  • QWebEngineView ne peut pas être utilisé à l'intérieur d'un FramelessWindow sous Windows en raison d'un bogue de rendu du GPU ; utiliser QGraphicsView comme alternative.
  • QOpenGLWidget peut entrer en conflit avec WA_TranslucentBackground sur les pilotes NVIDIA ; utiliser un logiciel de rendu si nécessaire.
  • SVG dans QSvgRenderer nécessite un QPainter actif ; ne pas appeler render en dehors de paintEvent.
  • QTimer pour l'animation : ne pas créer un nouveau minuteur à chaque frame ; utiliser QPropertyAnimation avec des propriétés.

12. Référence rapide (résumé pour l'agent)

ComposantCléComportement
FenêtreFramelessWindowHint+WA_TranslucentBackgroundDéplacement natif, redimensionnement par les bords
PanelQFrame#PanelOmbre 24/4, radius 16, contient tout sauf la marge
Barre de titreTopAppBar 56pxToggle, titre, boutons de fenêtre, déplacement+dbl-max
TiroirNavDrawer 220/56pxQPropertyAnimation minimumWidth, indicateur en pilule
BoutonMdButton filled/tonalCalque d'état, ondulation, 40px, pill
InterrupteurMdSwitch 52×32Cercle interpolé, coche On, calque d'état
CurseurMdSlider h=28Piste bicolore, cercle 20px, calque d'état
Case à cocherMdCheckBox h=24Coche progressivement, calque d'état, échelle
SegmentéMdSegmentedControl h=40Boutons radio, indicateur animé, séparateurs
DépôtFileDropAreaGlisser-déposer de fichiers, clic pour parcourir, glyphe animé
ChatQListWidget5 réponses, messages [ChatMessageWidget]
ThèmeTheme dataclassLIGHT/DARK switcher via QSS+palette
AnimationQPropertyAnimation + DUR_*OutCubic/OutQuad pour tout

13. Changements par rapport à PyQt5

  • Qt.Alignment — utiliser Qt.AlignmentFlag.AlignLeft.
  • Qt.MouseButton — Qt.MouseButton.LeftButton.
  • connect — button.clicked.connect(fn) ne nécessite plus de convertisseur de type.
  • QString n'existe pas ; utiliser str.
  • QDesktopWidget est supprimé ; utiliser QScreen.availableGeometry.
  • QStyleFactory.create — Fusion est intégré.

14. Dépendances et compatibilité

  • PyQt6 ≥ 6.5 (type hints, QPropertyAnimation).
  • Zéro dépendance externe pour l'interface utilisateur (pas de PyQtGraph, QtCharts, ni modules supplémentaires).
  • Matplotlib peut être installé (%pip install matplotlib), mais son intégration dans une fenêtre sans cadre n'est pas testée.
  • PyInstaller est recommandé pour la diffusion.
  • Testé sur Windows 10/11, Ubuntu 22.04, macOS 14. Les ombres natives macOS (NSWindow) ne sont pas utilisées ; la chute d'ombre est dessinée par Qt.

Fin de la spécification SKILL.md.

Skills associés