Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/pdf_equilibrist/ui/easter_egg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Easter-egg : 7 clics sur le logo de la barre de titre lancent une courte pluie « Matrix » verte."""
from __future__ import annotations
import math
import random

from PyQt6.QtWidgets import QWidget
from PyQt6.QtGui import QPainter, QColor, QFont
from PyQt6.QtCore import Qt, QTimer

ACCENT = (107, 191, 78) # vert du logo, #6BBF4E
CLICKS_NEEDED = 7
CLICK_WINDOW_MS = 2500 # délai max entre deux clics pour que la série continue
DURATION_MS = 4000
FRAME_MS = 45
CELL = 16 # taille d'une colonne / d'un caractère (px)
GLYPHS = "01ABCDEF<>/|=+*" # ASCII seulement : aucune police spéciale requise
# random sert uniquement à l'effet visuel (B311 : aucun usage cryptographique)


class MatrixRain(QWidget):
"""Calque transparent aux clics, posé sur une fenêtre, qui se détruit tout seul."""

def __init__(self, host: QWidget):
super().__init__(host)
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
self.setGeometry(host.rect())
self._cols = max(1, self.width() // CELL)
self._heads = [random.uniform(-30, 0) for _ in range(self._cols)] # nosec B311
self._speeds = [random.uniform(0.5, 1.4) for _ in range(self._cols)] # nosec B311
self._tick = 0
self._timer = QTimer(self)
self._timer.timeout.connect(self._step)
self._timer.start(FRAME_MS)
QTimer.singleShot(DURATION_MS, self._finish)
self.show()
self.raise_()

def _step(self):
self._tick += 1
for i in range(self._cols):
self._heads[i] += self._speeds[i]
self.update()

def _finish(self):
self._timer.stop()
self.hide()
self.deleteLater()

def paintEvent(self, _event):
p = QPainter(self)
font = QFont("Consolas")
font.setPixelSize(CELL - 2)
p.setFont(font)
# Fondu d'entrée et de sortie + pulsation du vert d'accent
t = self._tick * FRAME_MS / DURATION_MS
fade = min(1.0, t * 6, (1 - t) * 6) if 0 <= t <= 1 else 0
pulse = 0.75 + 0.25 * math.sin(self._tick * 0.35)
rows = self.height() // CELL + 1
for c in range(self._cols):
head = int(self._heads[c])
for k in range(14): # traînée de 14 caractères
r = head - k
if not 0 <= r < rows:
continue
alpha = int(255 * fade * pulse * (1 - k / 14))
col = QColor(255, 255, 255, alpha) if k == 0 else QColor(*ACCENT, alpha)
p.setPen(col)
p.drawText(c * CELL, r * CELL + CELL - 3, random.choice(GLYPHS)) # nosec B311
p.end()


def trigger(window: QWidget) -> None:
"""Lance la pluie sur ``window`` (ignoré si une est déjà en cours)."""
if window.findChild(MatrixRain) is None:
MatrixRain(window)
26 changes: 25 additions & 1 deletion src/pdf_equilibrist/ui/title_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
QFrame,
)
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont, QPen
from PyQt6.QtCore import Qt, pyqtSignal, QSize, QRectF
from PyQt6.QtCore import Qt, pyqtSignal, QSize, QRectF, QEvent
import time
from pdf_equilibrist import theme

ACCENT = "#6BBF4E"
Expand Down Expand Up @@ -225,6 +226,9 @@ def __init__(self, parent=None):
self._logo.setFixedSize(26, 38)
self._logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self._logo)
self._logo_clicks = 0 # easter-egg : voir ui/easter_egg.py
self._logo_last_click = 0.0
self._logo.installEventFilter(self)

# ── Nom de l'application ──────────────────────────────────────────────
self._app_name = QLabel("PDF Equilibrist")
Expand Down Expand Up @@ -408,6 +412,26 @@ def update_max_button(self, is_maximized: bool):

# ── Drag et double-clic ───────────────────────────────────────────────────

def eventFilter(self, obj, event):
"""Compte les clics sur le logo (7 de suite = easter-egg).

Un 2ᵉ clic rapide arrive en ``MouseButtonDblClick`` et un clic non traité remonte à la
barre (déplacement / agrandir-restaurer) : on absorbe donc les deux sur le logo.
"""
if obj is self._logo and event.type() in (QEvent.Type.MouseButtonPress,
QEvent.Type.MouseButtonDblClick):
from pdf_equilibrist.ui import easter_egg
now = time.monotonic()
if (now - self._logo_last_click) * 1000 > easter_egg.CLICK_WINDOW_MS:
self._logo_clicks = 0
self._logo_last_click = now
self._logo_clicks += 1
if self._logo_clicks >= easter_egg.CLICKS_NEEDED:
self._logo_clicks = 0
easter_egg.trigger(self.window())
return True
return False

def mousePressEvent(self, event):
"""
Lance le déplacement natif de la fenêtre au clic gauche.
Expand Down
Loading