Skip to content

Commit cf333fc

Browse files
committed
fix(ui): prevent QTimer GC crashes, refine tab slider, add Orin Nano Super recovery guide
- Add RuntimeError guards to all _tick/_on_tick methods across 14 files to eliminate SystemError during garbage collection when switching pages. - Remove green background block from AnimatedTabButton; use transparent bg with subtle green text + shortened underline for cleaner tab appearance. - Simplify _tab_slider gradient to solid rgba green, reducing visual noise. - Add _tab_slider to SkillsPage for consistent tab indicator UX. - Map orin-nano-devkit-super to classic recovery guide (J3011).
1 parent a6ee59d commit cf333fc

18 files changed

Lines changed: 187 additions & 194 deletions

seeed_jetson_develop/data/recovery_guides.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@
276276
# classic
277277
"j4012classic": "classic", "j4011classic": "classic",
278278
"j3011classic": "classic", "j3010classic": "classic",
279+
# Orin Nano Super official devkit shares the same recovery procedure as classic J3011
280+
"orin-nano-devkit-super": "classic",
279281
# industrial
280282
"j4012industrial": "industrial", "j4011industrial": "industrial",
281283
"j3011industrial": "industrial", "j3010industrial": "industrial",

seeed_jetson_develop/gui/flash_animation.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ def set_download_progress(self, ratio: float):
4545
self.set_progress(ratio)
4646

4747
def _on_tick(self):
48-
self._tick = (self._tick + 1) % 10000
49-
self.update()
48+
try:
49+
self._tick = (self._tick + 1) % 10000
50+
self.update()
51+
except RuntimeError:
52+
pass
5053

5154
def paintEvent(self, event):
5255
p = QPainter(self)

seeed_jetson_develop/gui/main_window_v2.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -654,18 +654,25 @@ def _build_sidebar(self):
654654
guide_btn = self._guide_btn
655655
guide_btn.setStyleSheet(f"""
656656
QPushButton {{
657-
background: transparent;
658-
border: none;
659-
color: {C_TEXT3};
657+
background: rgba(141,194,31,0.08);
658+
border: 1px solid rgba(141,194,31,0.25);
659+
color: {C_GREEN};
660660
font-size: {pt(10)}px;
661-
text-align: left;
662-
padding: 4px 0 4px {pt(20)}px;
663-
border-radius: 4px;
661+
font-weight: 600;
662+
text-align: center;
663+
padding: 6px 0;
664+
border-radius: 6px;
665+
margin: 0 {pt(16)}px;
664666
}}
665667
QPushButton:hover {{
666-
background: rgba(141,194,31,0.12);
668+
background: rgba(141,194,31,0.18);
669+
border-color: rgba(141,194,31,0.45);
667670
color: {C_GREEN};
668671
}}
672+
QPushButton:pressed {{
673+
background: rgba(141,194,31,0.25);
674+
border-color: rgba(141,194,31,0.55);
675+
}}
669676
""")
670677
guide_btn.clicked.connect(self._show_onboarding)
671678
lay.addWidget(guide_btn)

seeed_jetson_develop/gui/theme.py

Lines changed: 46 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from dataclasses import dataclass
1313

1414
from 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
1616
from 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

351354
class 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

487406
def 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

629541
def 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)

seeed_jetson_develop/gui/widgets/animated_checkbox.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ def setChecked(self, checked):
3535
self._timer.start(16)
3636

3737
def _tick(self):
38-
step = 0.18
39-
if self._target_progress > self._check_progress:
40-
self._check_progress = min(self._target_progress, self._check_progress + step)
41-
elif self._target_progress < self._check_progress:
42-
self._check_progress = max(self._target_progress, self._check_progress - step)
43-
else:
44-
self._timer.stop()
45-
self.update()
38+
try:
39+
step = 0.18
40+
if self._target_progress > self._check_progress:
41+
self._check_progress = min(self._target_progress, self._check_progress + step)
42+
elif self._target_progress < self._check_progress:
43+
self._check_progress = max(self._target_progress, self._check_progress - step)
44+
else:
45+
self._timer.stop()
46+
self.update()
47+
except RuntimeError:
48+
pass
4649

4750
def paintEvent(self, event):
4851
p = QPainter(self)

seeed_jetson_develop/gui/widgets/breathing_button.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ def __init__(self, text: str, parent=None):
2626
self.setCursor(Qt.PointingHandCursor)
2727

2828
def _tick(self):
29-
self._time = (self._time + self._speed) % 1.0
30-
self.update()
29+
try:
30+
self._time = (self._time + self._speed) % 1.0
31+
self.update()
32+
except RuntimeError:
33+
pass
3134

3235
def paintEvent(self, event):
3336
p = QPainter(self)

seeed_jetson_develop/gui/widgets/breathing_dot.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ def __init__(self, color: str = "#8DC21F", size: int = 10, parent=None):
3030
self.setStyleSheet("background:transparent;")
3131

3232
def _tick(self):
33-
self._time = (self._time + self._speed) % 1.0
34-
self.update()
33+
try:
34+
self._time = (self._time + self._speed) % 1.0
35+
self.update()
36+
except RuntimeError:
37+
pass
3538

3639
def paintEvent(self, event):
3740
p = QPainter(self)

seeed_jetson_develop/gui/widgets/breathing_logo.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ def __init__(self, parent=None):
2626
self.setStyleSheet("background:transparent;")
2727

2828
def _tick(self):
29-
self._time = (self._time + 0.012) % 1.0
30-
self.update()
29+
try:
30+
self._time = (self._time + 0.012) % 1.0
31+
self.update()
32+
except RuntimeError:
33+
pass
3134

3235
def paintEvent(self, event):
3336
p = QPainter(self)

seeed_jetson_develop/gui/widgets/content_bg_anim.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ def __init__(self, parent=None):
3737
self.setStyleSheet("background:transparent;")
3838

3939
def _tick(self):
40-
self._time = (self._time + 0.002) % 1.0
41-
self.update()
40+
try:
41+
self._time = (self._time + 0.002) % 1.0
42+
self.update()
43+
except RuntimeError:
44+
pass
4245

4346
def paintEvent(self, event):
4447
p = QPainter(self)

0 commit comments

Comments
 (0)