Module saePisan.view.components.CustomToast

Classes

class CustomToast (parent=None, title='', text='', duration=3000, position='top-right', icon_path=None)
Expand source code
class CustomToast(QWidget):
    """
    Custom Toast Notification Widget
    This widget displays a toast notification with a title, text, icon, and a progress bar.
    It can be customized with different positions, colors, and icons.
    The toast will automatically close after a specified duration, but can be paused by hovering over it.
    It supports various customization options such as border radius, icon size, background color, title color,
    close button icon color, duration bar color, and text color.
    Parameters:
        parent (QWidget): The parent widget for the toast.
        title (str): The title of the toast notification.
        text (str): The text content of the toast notification.
        duration (int): Duration in milliseconds before the toast automatically closes. Default is 3000 ms.
        position (str): Position of the toast on the screen. Options are "top-right", "top-left", "bottom-right", "bottom-left". Default is "top-right".
        icon_path (str): Path to the icon image to be displayed on the left side of the toast. Default is None, which uses a default icon.
    """
    def __init__(self, parent=None, title="", text="", duration=3000, position="top-right", icon_path=None):
        """Initialize the CustomToast widget."""
        super().__init__(parent)
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.Tool |
            Qt.WindowType.WindowStaysOnTopHint
        )
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
        self.is_paused = False

        # Main layout
        main_layout = QVBoxLayout(self)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        # Card container for solid background
        card = QWidget(self)
        card.setObjectName("toastCard")
        # Bawah 0 agar progressbar nempel ke bawah card
        card_layout = QVBoxLayout(card)
        card_layout.setContentsMargins(18, 14, 18, 0)
        card_layout.setSpacing(0)

        # Top area: icon kiri, garis, title & text kanan (pakai grid)
        top_grid = QGridLayout()
        top_grid.setHorizontalSpacing(12)
        top_grid.setVerticalSpacing(2)
        top_grid.setContentsMargins(0, 0, 0, 0)

        # Icon kiri
        icon_label = QLabel()
        if icon_path:
            pixmap = QPixmap(icon_path)
            icon_label.setPixmap(pixmap.scaled(36, 36, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
        else:
            pixmap = QPixmap("assets/information.svg")
            icon_label.setPixmap(pixmap.scaled(36, 36, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
        icon_label.setFixedSize(40, 40)
        icon_label.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
        icon_label.setStyleSheet("background: transparent;")
        top_grid.addWidget(icon_label, 0, 0, 2, 1, Qt.AlignmentFlag.AlignTop)

        # Garis vertikal pembatas
        line = QWidget()
        line.setFixedWidth(1)
        line.setStyleSheet("background: #b0bec5; margin-top: 2px; margin-bottom: 2px;")
        top_grid.addWidget(line, 0, 1, 2, 1)

        # Title kanan atas
        self.title_label = QLabel(title)
        self.title_label.setFont(QFont("Arial", 10, QFont.Weight.Bold))
        self.title_label.setStyleSheet("color: #222; background: transparent;")
        self.title_label.setWordWrap(True)
        top_grid.addWidget(self.title_label, 0, 2, 1, 2, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)

        # Close button kanan atas
        close_btn = QPushButton("✕")
        close_btn.setFixedSize(24, 24)
        close_btn.setStyleSheet("""
            QPushButton {
                border: none;
                background: transparent;
                font-size: 16px;
                color: #444;
            }
            QPushButton:hover {
                color: #d00;
                background: #e3f1fa;
            }
        """)
        close_btn.clicked.connect(self.close)
        top_grid.addWidget(close_btn, 0, 4, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)

        # Text kanan bawah
        self.text_label = QLabel(text)
        self.text_label.setFont(QFont("Arial", 8))
        self.text_label.setStyleSheet("color: #333; background: transparent;")
        self.text_label.setWordWrap(True)
        self.text_label.setMinimumWidth(250)
        self.text_label.setMaximumWidth(400)
        top_grid.addWidget(self.text_label, 1, 2, 1, 3, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)

        card_layout.addLayout(top_grid)
        card_layout.addSpacing(8)

        # Duration bar di paling bawah, tanpa space kosong
        self.progress = QProgressBar()
        self.progress.setMaximum(duration)
        self.progress.setValue(duration)
        self.progress.setTextVisible(False)
        self.progress.setFixedHeight(9)
        self.progress.setStyleSheet("""
            QProgressBar {
                border: none;
                background: #e0e0e0;
                border-radius: 3px;
                margin-bottom: 5px;
            }
            QProgressBar::chunk {
                background: #2196f3;
                border-radius: 3px;
            }
        """)
        card_layout.addWidget(self.progress)

        # Set card background and border
        card.setStyleSheet("""
            QWidget#toastCard {
                background: #eaf6fd;
                border: 1.5px solid #90caf9;
                border-radius: 0px;
            }
        """)

        main_layout.addWidget(card)

        self.adjustSize()
        self.duration = duration
        self.position = position

        # Timer for auto-close and progress bar
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_progress)
        self.timer.start(15)
        self.elapsed = 0

    def showEvent(self, event):
        """Handle the show event to adjust size and position."""
        super().showEvent(event)
        self.adjustSize()
        self.move_to_position()

    def move_to_position(self):
        """_summary_
        Move the toast to the specified position relative to its parent widget.
        If the parent widget is not set, it will center the toast on the screen.
        If the parent widget is set, it will position the toast at the specified corner with a margin.
        Supported positions are "top-right", "top-left", "bottom-right", "bottom-left", and "center".
        If the position is not recognized, it defaults to centering the toast on the parent widget.
        """
        parent = self.parentWidget()
        if parent:
            parent_rect = parent.geometry()
            margin = 32
            if self.position == "top-right":
                x = parent_rect.x() + parent_rect.width() - self.width() - margin
                y = parent_rect.y() + margin
            elif self.position == "top-left":
                x = parent_rect.x() + margin
                y = parent_rect.y() + margin
            elif self.position == "bottom-right":
                x = parent_rect.x() + parent_rect.width() - self.width() - margin
                y = parent_rect.y() + parent_rect.height() - self.height() - margin
            elif self.position == "bottom-left":
                x = parent_rect.x() + margin
                y = parent_rect.y() + parent_rect.height() - self.height() - margin
            else:
                x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
                y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
            self.move(x, y)

    def enterEvent(self, event):
        """Pause the toast when the mouse enters."""
        self.is_paused = True
        self.timer.stop()
        super().enterEvent(event)

    def leaveEvent(self, event):
        """Resume the toast when the mouse leaves."""
        self.is_paused = False
        self.timer.start(15)
        super().leaveEvent(event)

    def update_progress(self):
        """Update the progress bar and check if the toast should close."""
        if self.is_paused:
            return
        self.elapsed += 15
        remaining = max(0, self.duration - self.elapsed)
        self.progress.setValue(remaining)
        if self.elapsed >= self.duration:
            self.timer.stop()
            self.close()
    
    def set_border_radius(self, radius: int):
        """
        Set the border radius of the toast card.

        Args:
            radius (int): The desired border radius in pixels to be applied to the toast card.

        This method updates the stylesheet of the widget named "toastCard" to reflect the specified border radius.
        """
        style = f"""
            QWidget#toastCard {{
                background: #eaf6fd;
                border: 1.5px solid #90caf9;
                border-radius: {radius}px;
            }}
        """
        self.findChild(QWidget, "toastCard").setStyleSheet(style)
        
    def set_icon_size(self, width: int, height: int):
        """Atur ukuran icon pada toast."""
        icon_label = self.findChild(QLabel)
        if icon_label:
            pixmap = icon_label.pixmap()
            if pixmap:
                icon_label.setPixmap(pixmap.scaled(width, height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
            icon_label.setFixedSize(width, height)

    def set_background_color(self, color: str):
        """Atur warna background card toast."""
        card = self.findChild(QWidget, "toastCard")
        style = f"""
            QWidget#toastCard {{
                background: {color};
                border: 1.5px solid #90caf9;
                border-radius: 0px;
            }}
        """
        card.setStyleSheet(style)

    def set_title_color(self, color: str):
        """Atur warna teks title."""
        self.title_label.setStyleSheet(f"color: {color}; background: transparent;")

    def set_close_button_icon_color(self, color: str):
        """Atur warna icon tombol close."""
        close_btn = self.findChildren(QPushButton)[0]
        close_btn.setStyleSheet(f"""
            QPushButton {{
                border: none;
                background: transparent;
                font-size: 16px;
                color: {color};
            }}
            QPushButton:hover {{
                color: #d00;
                background: #e3f1fa;
            }}
        """)

    def set_duration_bar_color(self, color: str):
        """Atur warna progress bar durasi."""
        self.progress.setStyleSheet(f"""
            QProgressBar {{
                border: none;
                background: #e0e0e0;
                border-radius: 3px;
                margin-bottom: 5px;
            }}
            QProgressBar::chunk {{
                background: {color};
                border-radius: 3px;
            }}
        """)

    def set_text_color(self, color: str):
        """Atur warna teks isi toast."""
        self.text_label.setStyleSheet(f"color: {color}; background: transparent;")

Custom Toast Notification Widget This widget displays a toast notification with a title, text, icon, and a progress bar. It can be customized with different positions, colors, and icons. The toast will automatically close after a specified duration, but can be paused by hovering over it. It supports various customization options such as border radius, icon size, background color, title color, close button icon color, duration bar color, and text color.

Parameters

parent (QWidget): The parent widget for the toast. title (str): The title of the toast notification. text (str): The text content of the toast notification. duration (int): Duration in milliseconds before the toast automatically closes. Default is 3000 ms. position (str): Position of the toast on the screen. Options are "top-right", "top-left", "bottom-right", "bottom-left". Default is "top-right". icon_path (str): Path to the icon image to be displayed on the left side of the toast. Default is None, which uses a default icon.

Initialize the CustomToast widget.

Ancestors

  • PyQt6.QtWidgets.QWidget
  • PyQt6.QtCore.QObject
  • PyQt6.sip.wrapper
  • PyQt6.QtGui.QPaintDevice
  • PyQt6.sip.simplewrapper

Methods

def enterEvent(self, event)
Expand source code
def enterEvent(self, event):
    """Pause the toast when the mouse enters."""
    self.is_paused = True
    self.timer.stop()
    super().enterEvent(event)

Pause the toast when the mouse enters.

def leaveEvent(self, event)
Expand source code
def leaveEvent(self, event):
    """Resume the toast when the mouse leaves."""
    self.is_paused = False
    self.timer.start(15)
    super().leaveEvent(event)

Resume the toast when the mouse leaves.

def move_to_position(self)
Expand source code
def move_to_position(self):
    """_summary_
    Move the toast to the specified position relative to its parent widget.
    If the parent widget is not set, it will center the toast on the screen.
    If the parent widget is set, it will position the toast at the specified corner with a margin.
    Supported positions are "top-right", "top-left", "bottom-right", "bottom-left", and "center".
    If the position is not recognized, it defaults to centering the toast on the parent widget.
    """
    parent = self.parentWidget()
    if parent:
        parent_rect = parent.geometry()
        margin = 32
        if self.position == "top-right":
            x = parent_rect.x() + parent_rect.width() - self.width() - margin
            y = parent_rect.y() + margin
        elif self.position == "top-left":
            x = parent_rect.x() + margin
            y = parent_rect.y() + margin
        elif self.position == "bottom-right":
            x = parent_rect.x() + parent_rect.width() - self.width() - margin
            y = parent_rect.y() + parent_rect.height() - self.height() - margin
        elif self.position == "bottom-left":
            x = parent_rect.x() + margin
            y = parent_rect.y() + parent_rect.height() - self.height() - margin
        else:
            x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
            y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
        self.move(x, y)

summary Move the toast to the specified position relative to its parent widget. If the parent widget is not set, it will center the toast on the screen. If the parent widget is set, it will position the toast at the specified corner with a margin. Supported positions are "top-right", "top-left", "bottom-right", "bottom-left", and "center". If the position is not recognized, it defaults to centering the toast on the parent widget.

def set_background_color(self, color: str)
Expand source code
def set_background_color(self, color: str):
    """Atur warna background card toast."""
    card = self.findChild(QWidget, "toastCard")
    style = f"""
        QWidget#toastCard {{
            background: {color};
            border: 1.5px solid #90caf9;
            border-radius: 0px;
        }}
    """
    card.setStyleSheet(style)

Atur warna background card toast.

def set_border_radius(self, radius: int)
Expand source code
def set_border_radius(self, radius: int):
    """
    Set the border radius of the toast card.

    Args:
        radius (int): The desired border radius in pixels to be applied to the toast card.

    This method updates the stylesheet of the widget named "toastCard" to reflect the specified border radius.
    """
    style = f"""
        QWidget#toastCard {{
            background: #eaf6fd;
            border: 1.5px solid #90caf9;
            border-radius: {radius}px;
        }}
    """
    self.findChild(QWidget, "toastCard").setStyleSheet(style)

Set the border radius of the toast card.

Args

radius : int
The desired border radius in pixels to be applied to the toast card.

This method updates the stylesheet of the widget named "toastCard" to reflect the specified border radius.

def set_close_button_icon_color(self, color: str)
Expand source code
def set_close_button_icon_color(self, color: str):
    """Atur warna icon tombol close."""
    close_btn = self.findChildren(QPushButton)[0]
    close_btn.setStyleSheet(f"""
        QPushButton {{
            border: none;
            background: transparent;
            font-size: 16px;
            color: {color};
        }}
        QPushButton:hover {{
            color: #d00;
            background: #e3f1fa;
        }}
    """)

Atur warna icon tombol close.

def set_duration_bar_color(self, color: str)
Expand source code
def set_duration_bar_color(self, color: str):
    """Atur warna progress bar durasi."""
    self.progress.setStyleSheet(f"""
        QProgressBar {{
            border: none;
            background: #e0e0e0;
            border-radius: 3px;
            margin-bottom: 5px;
        }}
        QProgressBar::chunk {{
            background: {color};
            border-radius: 3px;
        }}
    """)

Atur warna progress bar durasi.

def set_icon_size(self, width: int, height: int)
Expand source code
def set_icon_size(self, width: int, height: int):
    """Atur ukuran icon pada toast."""
    icon_label = self.findChild(QLabel)
    if icon_label:
        pixmap = icon_label.pixmap()
        if pixmap:
            icon_label.setPixmap(pixmap.scaled(width, height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
        icon_label.setFixedSize(width, height)

Atur ukuran icon pada toast.

def set_text_color(self, color: str)
Expand source code
def set_text_color(self, color: str):
    """Atur warna teks isi toast."""
    self.text_label.setStyleSheet(f"color: {color}; background: transparent;")

Atur warna teks isi toast.

def set_title_color(self, color: str)
Expand source code
def set_title_color(self, color: str):
    """Atur warna teks title."""
    self.title_label.setStyleSheet(f"color: {color}; background: transparent;")

Atur warna teks title.

def showEvent(self, event)
Expand source code
def showEvent(self, event):
    """Handle the show event to adjust size and position."""
    super().showEvent(event)
    self.adjustSize()
    self.move_to_position()

Handle the show event to adjust size and position.

def update_progress(self)
Expand source code
def update_progress(self):
    """Update the progress bar and check if the toast should close."""
    if self.is_paused:
        return
    self.elapsed += 15
    remaining = max(0, self.duration - self.elapsed)
    self.progress.setValue(remaining)
    if self.elapsed >= self.duration:
        self.timer.stop()
        self.close()

Update the progress bar and check if the toast should close.