11"""
22Drag-and-Drop Zone Widget
33
4- Handles PDF file drops for processing .
4+ Handles PDF file drops with animated glow effects .
55"""
6- from PyQt6 .QtWidgets import QWidget , QVBoxLayout , QLabel
7- from PyQt6 .QtCore import Qt , pyqtSignal
8- from PyQt6 .QtGui import QDragEnterEvent , QDropEvent
6+ from PyQt6 .QtWidgets import QWidget , QVBoxLayout , QLabel , QGraphicsDropShadowEffect , QFileDialog
7+ from PyQt6 .QtCore import Qt , pyqtSignal , QPropertyAnimation , QEasingCurve , QTimer , pyqtProperty
8+ from PyQt6 .QtGui import QDragEnterEvent , QDropEvent , QColor , QMouseEvent
99import qtawesome as qta
1010
1111
1212class DropZone (QWidget ):
13- """Widget for drag-and-drop PDF files."""
13+ """Widget for drag-and-drop PDF files with animated glow effects ."""
1414
1515 files_dropped = pyqtSignal (list ) # List of file paths
1616
1717 def __init__ (self ):
1818 super ().__init__ ()
1919 self .setAcceptDrops (True )
20+ self ._glow_intensity = 0.0
2021 self ._setup_ui ()
22+ self ._setup_glow_effect ()
23+ self ._setup_animations ()
2124
2225 def _setup_ui (self ):
2326 """Initialize UI."""
2427 layout = QVBoxLayout (self )
2528 layout .setAlignment (Qt .AlignmentFlag .AlignCenter )
2629
2730 # Icon
28- icon_label = QLabel ()
29- icon_label .setPixmap (qta .icon ('fa5s.cloud-upload-alt' , color = '#3B82F6' ).pixmap (64 , 64 ))
30- icon_label .setAlignment (Qt .AlignmentFlag .AlignCenter )
31- layout .addWidget (icon_label )
31+ self . icon_label = QLabel ()
32+ self . icon_label .setPixmap (qta .icon ('fa5s.cloud-upload-alt' , color = '#3B82F6' ).pixmap (64 , 64 ))
33+ self . icon_label .setAlignment (Qt .AlignmentFlag .AlignCenter )
34+ layout .addWidget (self . icon_label )
3235
3336 # Text
34- text = QLabel ("Drop PDF files here" )
35- text .setStyleSheet ("font-size: 18px; color: #A0A0A0; margin-top: 10px;" )
36- text .setAlignment (Qt .AlignmentFlag .AlignCenter )
37- layout .addWidget (text )
37+ self . text_label = QLabel ("Drop PDF files here" )
38+ self . text_label .setStyleSheet ("font-size: 18px; color: #A0A0A0; margin-top: 10px;" )
39+ self . text_label .setAlignment (Qt .AlignmentFlag .AlignCenter )
40+ layout .addWidget (self . text_label )
3841
39- subtext = QLabel ("or click to browse" )
40- subtext .setStyleSheet ("font-size: 14px; color: #666;" )
41- subtext .setAlignment (Qt .AlignmentFlag .AlignCenter )
42- layout .addWidget (subtext )
42+ self . subtext_label = QLabel ("or click to browse" )
43+ self . subtext_label .setStyleSheet ("font-size: 14px; color: #666;" )
44+ self . subtext_label .setAlignment (Qt .AlignmentFlag .AlignCenter )
45+ layout .addWidget (self . subtext_label )
4346
4447 self ._apply_styles ()
4548
49+ def _setup_glow_effect (self ):
50+ """Setup the glow drop shadow effect."""
51+ self .glow_effect = QGraphicsDropShadowEffect (self )
52+ self .glow_effect .setBlurRadius (0 )
53+ self .glow_effect .setColor (QColor (59 , 130 , 246 , 180 )) # #3B82F6 with alpha
54+ self .glow_effect .setOffset (0 , 0 )
55+ self .setGraphicsEffect (self .glow_effect )
56+
57+ def _setup_animations (self ):
58+ """Setup glow animations."""
59+ # Glow pulse animation for idle state
60+ self .pulse_timer = QTimer (self )
61+ self .pulse_timer .timeout .connect (self ._pulse_glow )
62+ self .pulse_direction = 1
63+ self .pulse_timer .start (50 ) # 50ms intervals for smooth animation
64+
65+ # Drag hover animation
66+ self .glow_animation = QPropertyAnimation (self , b"glowIntensity" )
67+ self .glow_animation .setDuration (200 )
68+ self .glow_animation .setEasingCurve (QEasingCurve .Type .OutCubic )
69+
70+ def _pulse_glow (self ):
71+ """Subtle idle pulse effect."""
72+ if hasattr (self , '_is_dragging' ) and self ._is_dragging :
73+ return # Don't pulse during drag
74+
75+ current = self .glow_effect .blurRadius ()
76+ step = 0.5 * self .pulse_direction
77+
78+ if current >= 15 :
79+ self .pulse_direction = - 1
80+ elif current <= 5 :
81+ self .pulse_direction = 1
82+
83+ self .glow_effect .setBlurRadius (max (0 , current + step ))
84+
85+ @pyqtProperty (float )
86+ def glowIntensity (self ):
87+ return self ._glow_intensity
88+
89+ @glowIntensity .setter
90+ def glowIntensity (self , value ):
91+ self ._glow_intensity = value
92+ self .glow_effect .setBlurRadius (value )
93+
4694 def _apply_styles (self ):
4795 """Apply drop zone styles."""
4896 self .setStyleSheet ("""
@@ -54,30 +102,65 @@ def _apply_styles(self):
54102 }
55103 DropZone:hover {
56104 border-color: #60A5FA;
105+ background-color: #1E293B;
106+ }
107+ DropZone[dragging="true"] {
108+ border: 3px solid #60A5FA;
57109 background-color: #1E3A5F;
58110 }
59111 """ )
60112
113+ def _animate_glow_in (self ):
114+ """Animate glow when dragging over."""
115+ self .glow_animation .stop ()
116+ self .glow_animation .setStartValue (self .glow_effect .blurRadius ())
117+ self .glow_animation .setEndValue (40 )
118+ self .glow_animation .start ()
119+ self .glow_effect .setColor (QColor (96 , 165 , 250 , 220 )) # Brighter blue
120+
121+ def _animate_glow_out (self ):
122+ """Animate glow back to normal."""
123+ self .glow_animation .stop ()
124+ self .glow_animation .setStartValue (self .glow_effect .blurRadius ())
125+ self .glow_animation .setEndValue (10 )
126+ self .glow_animation .start ()
127+ self .glow_effect .setColor (QColor (59 , 130 , 246 , 180 ))
128+
61129 def dragEnterEvent (self , event : QDragEnterEvent ):
62- """Handle drag enter."""
130+ """Handle drag enter with glow animation ."""
63131 if event .mimeData ().hasUrls ():
64- # Check if any URLs are PDFs
65132 for url in event .mimeData ().urls ():
66133 if url .toLocalFile ().lower ().endswith ('.pdf' ):
67134 event .acceptProposedAction ()
135+ self ._is_dragging = True
68136 self .setProperty ("dragging" , True )
69137 self .style ().polish (self )
138+ self ._animate_glow_in ()
139+ # Update icon color
140+ self .icon_label .setPixmap (
141+ qta .icon ('fa5s.cloud-upload-alt' , color = '#60A5FA' ).pixmap (72 , 72 )
142+ )
70143 return
71144
72145 def dragLeaveEvent (self , event ):
73146 """Handle drag leave."""
147+ self ._is_dragging = False
74148 self .setProperty ("dragging" , False )
75149 self .style ().polish (self )
150+ self ._animate_glow_out ()
151+ self .icon_label .setPixmap (
152+ qta .icon ('fa5s.cloud-upload-alt' , color = '#3B82F6' ).pixmap (64 , 64 )
153+ )
76154
77155 def dropEvent (self , event : QDropEvent ):
78156 """Handle file drop."""
157+ self ._is_dragging = False
79158 self .setProperty ("dragging" , False )
80159 self .style ().polish (self )
160+ self ._animate_glow_out ()
161+ self .icon_label .setPixmap (
162+ qta .icon ('fa5s.cloud-upload-alt' , color = '#3B82F6' ).pixmap (64 , 64 )
163+ )
81164
82165 pdf_files = []
83166 for url in event .mimeData ().urls ():
@@ -87,3 +170,15 @@ def dropEvent(self, event: QDropEvent):
87170
88171 if pdf_files :
89172 self .files_dropped .emit (pdf_files )
173+
174+ def mousePressEvent (self , event : QMouseEvent ):
175+ """Handle click to open file dialog."""
176+ if event .button () == Qt .MouseButton .LeftButton :
177+ files , _ = QFileDialog .getOpenFileNames (
178+ self ,
179+ "Select PDF Files" ,
180+ "" ,
181+ "PDF Files (*.pdf)"
182+ )
183+ if files :
184+ self .files_dropped .emit (files )
0 commit comments