Add SuccessToast as notifications

This commit is contained in:
hyyz17200
2026-05-08 01:05:04 +08:00
parent 614c65b30e
commit 5f3b5c716c
+89 -3
View File
@@ -11,7 +11,7 @@ if sys.platform.startswith("win") and "QT_QPA_PLATFORM" not in os.environ:
os.environ["QT_QPA_PLATFORM"] = "windows:fontengine=freetype" os.environ["QT_QPA_PLATFORM"] = "windows:fontengine=freetype"
try: try:
from PySide6.QtCore import QEvent, Qt, QUrl from PySide6.QtCore import QEasingCurve, QEvent, QPropertyAnimation, Qt, QTimer, QUrl
from PySide6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QFont from PySide6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QFont
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QApplication,
@@ -20,6 +20,7 @@ try:
QComboBox, QComboBox,
QFileDialog, QFileDialog,
QFormLayout, QFormLayout,
QGraphicsOpacityEffect,
QGroupBox, QGroupBox,
QHBoxLayout, QHBoxLayout,
QHeaderView, QHeaderView,
@@ -63,6 +64,87 @@ from .planning import (
SUMMARY_ROLE = Qt.UserRole + 100 SUMMARY_ROLE = Qt.UserRole + 100
class SuccessToast(QLabel):
FADE_MS = 1000
HOLD_MS = 5000
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
self.setAlignment(Qt.AlignCenter)
self.setWordWrap(True)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
self.setStyleSheet(
"""
QLabel {
background: #f4fff6;
color: #183f22;
border: 3px solid #2e7d32;
border-radius: 8px;
padding: 20px 32px;
font-size: 18px;
font-weight: 700;
}
"""
)
self._opacity_effect = QGraphicsOpacityEffect(self)
self.setGraphicsEffect(self._opacity_effect)
self._fade_in = QPropertyAnimation(self._opacity_effect, b"opacity", self)
self._fade_in.setDuration(self.FADE_MS)
self._fade_in.setEasingCurve(QEasingCurve.InOutQuad)
self._fade_out = QPropertyAnimation(self._opacity_effect, b"opacity", self)
self._fade_out.setDuration(self.FADE_MS)
self._fade_out.setEasingCurve(QEasingCurve.InOutQuad)
self._fade_out.finished.connect(self.hide)
self._hold_timer = QTimer(self)
self._hold_timer.setSingleShot(True)
self._hold_timer.timeout.connect(self._start_fade_out)
parent.installEventFilter(self)
self.hide()
def show_message(self, message: str) -> None:
self._hold_timer.stop()
self._fade_in.stop()
self._fade_out.stop()
self.setText(message)
self._fit_to_parent()
self._position()
self._opacity_effect.setOpacity(0.0)
self.show()
self.raise_()
self._fade_in.setStartValue(0.0)
self._fade_in.setEndValue(1.0)
self._fade_in.start()
self._hold_timer.start(self.FADE_MS + self.HOLD_MS)
def eventFilter(self, watched: object, event: QEvent) -> bool:
if watched is self.parentWidget() and event.type() == QEvent.Resize and self.isVisible():
self._fit_to_parent()
self._position()
return super().eventFilter(watched, event)
def _fit_to_parent(self) -> None:
parent = self.parentWidget()
if parent is None:
return
self.setMaximumWidth(max(360, min(720, parent.width() - 48)))
self.adjustSize()
def _position(self) -> None:
parent = self.parentWidget()
if parent is None:
return
margin = 24
x = max(margin, (parent.width() - self.width()) // 2)
y = max(margin, parent.height() - self.height() - margin)
self.move(x, y)
def _start_fade_out(self) -> None:
self._fade_out.stop()
self._fade_out.setStartValue(self._opacity_effect.opacity())
self._fade_out.setEndValue(0.0)
self._fade_out.start()
class DropTableWidget(QTableWidget): class DropTableWidget(QTableWidget):
def __init__(self, on_files_dropped: Callable[[list[Path]], None], parent: QWidget | None = None) -> None: def __init__(self, on_files_dropped: Callable[[list[Path]], None], parent: QWidget | None = None) -> None:
super().__init__(parent) super().__init__(parent)
@@ -323,6 +405,7 @@ class MainWindow(QMainWindow):
root.addWidget(self.log) root.addWidget(self.log)
self.setCentralWidget(central) self.setCentralWidget(central)
self.success_toast = SuccessToast(central)
def connect_option_signals(self) -> None: def connect_option_signals(self) -> None:
self.swap_gcode_combo.currentIndexChanged.connect(self.save_current_settings) self.swap_gcode_combo.currentIndexChanged.connect(self.save_current_settings)
@@ -749,6 +832,9 @@ class MainWindow(QMainWindow):
self.log.append(f"Estimated source filament: {format_filament(result.total_weight_grams)}") self.log.append(f"Estimated source filament: {format_filament(result.total_weight_grams)}")
self.log.append(f"G-code MD5: {result.gcode_md5}") self.log.append(f"G-code MD5: {result.gcode_md5}")
def show_success_toast(self, message: str) -> None:
self.success_toast.show_message(message)
def build_output(self) -> None: def build_output(self) -> None:
jobs = self.collect_jobs() jobs = self.collect_jobs()
if not jobs: if not jobs:
@@ -770,7 +856,7 @@ class MainWindow(QMainWindow):
options = self.build_options_for_output(output_path) options = self.build_options_for_output(output_path)
result = build_packed_3mf(jobs, options) result = build_packed_3mf(jobs, options)
self.log_build_result(result) self.log_build_result(result)
QMessageBox.information(self, APP_NAME, "The packed 3MF file was created.") self.show_success_toast("The packed 3MF file was created.")
if self.clear_after_build_check.isChecked(): if self.clear_after_build_check.isChecked():
self.remove_all() self.remove_all()
@@ -800,7 +886,7 @@ class MainWindow(QMainWindow):
+ ("\n..." if len(errors) > 8 else ""), + ("\n..." if len(errors) > 8 else ""),
) )
return return
QMessageBox.information(self, APP_NAME, f"Created {success_count} packed 3MF file(s).") self.show_success_toast(f"Created {success_count} packed 3MF file(s).")
if self.clear_after_build_check.isChecked(): if self.clear_after_build_check.isChecked():
self.remove_all() self.remove_all()