1212from dataclasses import dataclass
1313
1414from PyQt5 .QtCore import Qt , QRect , QPoint , QPointF , QTimer
15- from PyQt5 .QtGui import QColor , QFont , QFontDatabase , QPainter , QPen
15+ from PyQt5 .QtGui import QColor , QFont , QFontDatabase , QLinearGradient , QPainter , QPen
1616from PyQt5 .QtWidgets import (
1717 QApplication , QDialog , QFrame , QGraphicsDropShadowEffect ,
1818 QHBoxLayout , QLabel , QMessageBox , QProgressBar , QPushButton , QScrollArea , QWidget , QVBoxLayout ,
@@ -225,16 +225,19 @@ def mouseReleaseEvent(self, event):
225225 super ().mouseReleaseEvent (event )
226226
227227 def _tick (self ):
228- alive = []
229- for r in self ._ripples :
230- r ["radius" ] += r ["speed" ]
231- r ["opacity" ] -= r ["fade" ]
232- if r ["opacity" ] > 0 :
233- alive .append (r )
234- self ._ripples = alive
235- if not alive :
236- self ._ripple_timer .stop ()
237- self .update ()
228+ try :
229+ alive = []
230+ for r in self ._ripples :
231+ r ["radius" ] += r ["speed" ]
232+ r ["opacity" ] -= r ["fade" ]
233+ if r ["opacity" ] > 0 :
234+ alive .append (r )
235+ self ._ripples = alive
236+ if not alive :
237+ self ._ripple_timer .stop ()
238+ self .update ()
239+ except RuntimeError :
240+ pass
238241
239242 def paintEvent (self , event ):
240243 super ().paintEvent (event )
@@ -349,11 +352,10 @@ def make_button(text: str, primary: bool = False,
349352
350353
351354class HoverCard (QFrame ):
352- """带悬浮阴影呼吸动画的卡片
355+ """带悬浮阴影的卡片
353356
354- hover 时阴影扩大+下沉,模拟卡片"浮起"效果。
355- 使用 QTimer 逐步修改 QGraphicsDropShadowEffect 属性,
356- 每次修改前通过 sip.isdeleted 防御检查,避免 C++ 对象已删导致的崩溃。
357+ hover 时阴影直接切换为较大值(无动画过渡),避免 QGraphicsDropShadowEffect
358+ 与 QTimer 快速重绘在 Windows 下触发 painter 冲突导致段错误。
357359 """
358360 def __init__ (self , radius : int = 12 , with_shadow : bool = True , parent = None ):
359361 super ().__init__ (parent )
@@ -375,15 +377,6 @@ def __init__(self, radius: int = 12, with_shadow: bool = True, parent=None):
375377 }}
376378 """ )
377379 self ._shadow = None
378- self ._shadow_timer = QTimer (self )
379- self ._shadow_timer .timeout .connect (self ._tick_shadow )
380- # 动画状态
381- self ._blur_target = 28.0
382- self ._blur_current = 28.0
383- self ._y_target = 6.0
384- self ._y_current = 6.0
385- self ._alpha_target = 80
386- self ._alpha_current = 80
387380 if with_shadow :
388381 self ._setup_shadow ()
389382
@@ -396,93 +389,19 @@ def _setup_shadow(self):
396389 self ._shadow_effect = self ._shadow # 兼容 clear_shadow
397390
398391 def enterEvent (self , event ):
399- self ._blur_target = 48.0
400- self ._y_target = 10.0
401- self ._alpha_target = 110
402- if not self ._shadow_timer .isActive ():
403- self ._shadow_timer .start (16 )
392+ if self ._shadow is not None :
393+ self ._shadow .setBlurRadius (42 )
394+ self ._shadow .setOffset (QPointF (0 , 10 ))
395+ self ._shadow .setColor (QColor (0 , 0 , 0 , 100 ))
404396 super ().enterEvent (event )
405397
406398 def leaveEvent (self , event ):
407- self ._blur_target = 28.0
408- self ._y_target = 6.0
409- self ._alpha_target = 80
410- if not self ._shadow_timer .isActive ():
411- self ._shadow_timer .start (16 )
399+ if self ._shadow is not None :
400+ self ._shadow .setBlurRadius (28 )
401+ self ._shadow .setOffset (QPointF (0 , 6 ))
402+ self ._shadow .setColor (QColor (0 , 0 , 0 , 80 ))
412403 super ().leaveEvent (event )
413404
414- def _tick_shadow (self ):
415- """每帧逐步逼近目标阴影参数,带 C++ 对象存活检查。"""
416- if self ._shadow is None :
417- self ._shadow_timer .stop ()
418- return
419- try :
420- from PyQt5 import sip
421- if sip .isdeleted (self ._shadow ):
422- self ._shadow = None
423- self ._shadow_timer .stop ()
424- return
425- except Exception :
426- self ._shadow_timer .stop ()
427- return
428-
429- step_blur = 3.0
430- step_y = 0.5
431- step_alpha = 4
432- done = True
433-
434- # blurRadius
435- diff = self ._blur_target - self ._blur_current
436- if abs (diff ) > 0.5 :
437- self ._blur_current += step_blur if diff > 0 else - step_blur
438- # 防止超调
439- if diff > 0 and self ._blur_current > self ._blur_target :
440- self ._blur_current = self ._blur_target
441- elif diff < 0 and self ._blur_current < self ._blur_target :
442- self ._blur_current = self ._blur_target
443- done = False
444- try :
445- self ._shadow .setBlurRadius (int (self ._blur_current ))
446- except RuntimeError :
447- self ._shadow = None
448- self ._shadow_timer .stop ()
449- return
450-
451- # offset y
452- diff_y = self ._y_target - self ._y_current
453- if abs (diff_y ) > 0.1 :
454- self ._y_current += step_y if diff_y > 0 else - step_y
455- if diff_y > 0 and self ._y_current > self ._y_target :
456- self ._y_current = self ._y_target
457- elif diff_y < 0 and self ._y_current < self ._y_target :
458- self ._y_current = self ._y_target
459- done = False
460- try :
461- self ._shadow .setOffset (QPointF (0 , self ._y_current ))
462- except RuntimeError :
463- self ._shadow = None
464- self ._shadow_timer .stop ()
465- return
466-
467- # color alpha
468- diff_a = self ._alpha_target - self ._alpha_current
469- if abs (diff_a ) > 0 :
470- self ._alpha_current += step_alpha if diff_a > 0 else - step_alpha
471- if diff_a > 0 and self ._alpha_current > self ._alpha_target :
472- self ._alpha_current = self ._alpha_target
473- elif diff_a < 0 and self ._alpha_current < self ._alpha_target :
474- self ._alpha_current = self ._alpha_target
475- done = False
476- try :
477- self ._shadow .setColor (QColor (0 , 0 , 0 , int (self ._alpha_current )))
478- except RuntimeError :
479- self ._shadow = None
480- self ._shadow_timer .stop ()
481- return
482-
483- if done :
484- self ._shadow_timer .stop ()
485-
486405
487406def make_card (radius : int = 12 , with_shadow : bool = True ) -> QFrame :
488407 """创建卡片 - 带高光顶边、立体阴影和 hover 阴影呼吸动画"""
@@ -574,12 +493,12 @@ def __init__(self, text: str, active: bool = False, parent=None):
574493 self ._apply_style ()
575494
576495 def _apply_style (self ):
577- bg = "rgba(122,179,23,0.15)" if self . _active else "transparent"
496+ # 选中态:无背景块,仅用文字颜色 + 底部下划线标识,更 subtle
578497 color = C_GREEN if self ._active else C_TEXT2
579- weight = "600 " if self ._active else "400"
498+ weight = "500 " if self ._active else "400"
580499 self .setStyleSheet (f"""
581500 QPushButton {{
582- background: { bg } ;
501+ background: transparent ;
583502 color: { color } ;
584503 border: none;
585504 border-radius: 0px;
@@ -589,7 +508,7 @@ def _apply_style(self):
589508 min-height: { pt (36 )} px;
590509 text-align: center;
591510 }}
592- QPushButton:hover {{ background: rgba(255,255,255,0.06 ); color:{ C_TEXT } ; }}
511+ QPushButton:hover {{ background: rgba(255,255,255,0.04 ); color:{ C_TEXT } ; }}
593512 """ )
594513
595514 def setActive (self , active : bool ):
@@ -602,28 +521,21 @@ def setActive(self, active: bool):
602521 self ._timer .start (16 )
603522
604523 def _tick (self ):
605- step = 0.18
606- if self ._target_progress > self ._underline_progress :
607- self ._underline_progress = min (self ._target_progress , self ._underline_progress + step )
608- elif self ._target_progress < self ._underline_progress :
609- self ._underline_progress = max (self ._target_progress , self ._underline_progress - step )
610- else :
611- self ._timer .stop ()
612- self .update ()
524+ try :
525+ step = 0.18
526+ if self ._target_progress > self ._underline_progress :
527+ self ._underline_progress = min (self ._target_progress , self ._underline_progress + step )
528+ elif self ._target_progress < self ._underline_progress :
529+ self ._underline_progress = max (self ._target_progress , self ._underline_progress - step )
530+ else :
531+ self ._timer .stop ()
532+ self .update ()
533+ except RuntimeError :
534+ pass
613535
614536 def paintEvent (self , event ):
537+ # 下划线由外部 _tab_slider 统一绘制,避免与跨按钮滑块重叠
615538 super ().paintEvent (event )
616- if self ._underline_progress < 0.01 :
617- return
618- p = QPainter (self )
619- p .setRenderHint (QPainter .Antialiasing )
620- w , h = self .width (), self .height ()
621- line_w = int (w * 0.55 * self ._underline_progress )
622- line_x = (w - line_w ) // 2
623- line_y = h - 2
624- alpha = int (200 * self ._underline_progress )
625- p .setPen (QPen (QColor (141 , 194 , 31 , alpha ), 2 , Qt .SolidLine , Qt .RoundCap ))
626- p .drawLine (line_x , line_y , line_x + line_w , line_y )
627539
628540
629541def make_tab_button (text : str , active : bool = False ) -> "QPushButton" :
@@ -646,8 +558,11 @@ def __init__(self, parent=None):
646558 self .setTextVisible (False )
647559
648560 def _tick (self ):
649- self ._shine_pos = (self ._shine_pos + 0.02 ) % 1.0
650- self .update ()
561+ try :
562+ self ._shine_pos = (self ._shine_pos + 0.02 ) % 1.0
563+ self .update ()
564+ except RuntimeError :
565+ pass
651566
652567 def paintEvent (self , event ):
653568 p = QPainter (self )
0 commit comments