Beispiel #1
0
class MDrawer(QWidget):
    """
    A panel which slides in from the edge of the screen.
    """
    LeftPos = 'left'
    RightPos = 'right'
    TopPos = 'top'
    BottomPos = 'bottom'

    sig_closed = Signal()

    def __init__(self, title, position='right', closable=True, parent=None):
        super(MDrawer, self).__init__(parent)
        self.setObjectName('message')
        self.setWindowFlags(Qt.Popup)
        # self.setWindowFlags(
        #     Qt.FramelessWindowHint | Qt.Popup | Qt.WA_TranslucentBackground)
        self.setAttribute(Qt.WA_StyledBackground)

        self._title_label = MLabel(parent=self).h4()
        # self._title_label.set_elide_mode(Qt.ElideRight)
        self._title_label.setText(title)

        self._close_button = MToolButton(
            parent=self).icon_only().svg('close_line.svg').small()
        self._close_button.clicked.connect(self.close)
        self._close_button.setVisible(closable or False)

        _title_lay = QHBoxLayout()
        _title_lay.addWidget(self._title_label)
        _title_lay.addStretch()
        _title_lay.addWidget(self._close_button)
        self._button_lay = QHBoxLayout()
        self._button_lay.addStretch()

        self._scroll_area = QScrollArea()
        self._main_lay = QVBoxLayout()
        self._main_lay.addLayout(_title_lay)
        self._main_lay.addWidget(MDivider())
        self._main_lay.addWidget(self._scroll_area)
        self._main_lay.addWidget(MDivider())
        self._main_lay.addLayout(self._button_lay)
        self.setLayout(self._main_lay)

        self._position = position

        self._close_timer = QTimer(self)
        self._close_timer.setSingleShot(True)
        self._close_timer.timeout.connect(self.close)
        self._close_timer.timeout.connect(self.sig_closed)
        self._close_timer.setInterval(300)
        self._is_first_close = True

        self._pos_ani = QPropertyAnimation(self)
        self._pos_ani.setTargetObject(self)
        self._pos_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._pos_ani.setDuration(300)
        self._pos_ani.setPropertyName('pos')

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName('windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(1.0)
        # self._shadow_effect = QGraphicsDropShadowEffect(self)
        # color = dayu_theme.red
        # self._shadow_effect.setColor(color)
        # self._shadow_effect.setOffset(0, 0)
        # self._shadow_effect.setBlurRadius(5)
        # self._shadow_effect.setEnabled(False)
        # self.setGraphicsEffect(self._shadow_effect)

    def set_widget(self, widget):
        self._scroll_area.setWidget(widget)

    def add_button(self, button):
        self._button_lay.addWidget(button)

    def _fade_out(self):
        self._pos_ani.setDirection(QAbstractAnimation.Backward)
        self._pos_ani.start()
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._pos_ani.start()
        self._opacity_ani.start()

    def _set_proper_position(self):
        parent = self.parent()
        parent_geo = parent.geometry()
        if self._position == MDrawer.LeftPos:
            pos = parent_geo.topLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topLeft())
            target_x = pos.x()
            target_y = pos.y()
            self.setFixedHeight(parent_geo.height())
            self._pos_ani.setStartValue(
                QPoint(target_x - self.width(), target_y))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.RightPos:
            pos = parent_geo.topRight(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topRight())
            self.setFixedHeight(parent_geo.height())
            target_x = pos.x() - self.width()
            target_y = pos.y()
            self._pos_ani.setStartValue(
                QPoint(target_x + self.width(), target_y))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.TopPos:
            pos = parent_geo.topLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topLeft())
            self.setFixedWidth(parent_geo.width())
            target_x = pos.x()
            target_y = pos.y()
            self._pos_ani.setStartValue(
                QPoint(target_x, target_y - self.height()))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.BottomPos:
            pos = parent_geo.bottomLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.bottomLeft())
            self.setFixedWidth(parent_geo.width())
            target_x = pos.x()
            target_y = pos.y() - self.height()
            self._pos_ani.setStartValue(
                QPoint(target_x, target_y + self.height()))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))

    def set_dayu_position(self, value):
        """
        Set the placement of the MDrawer.
        top/right/bottom/left, default is right
        :param value: str
        :return: None
        """
        self._position = value
        if value in [MDrawer.BottomPos, MDrawer.TopPos]:
            self.setFixedHeight(200)
        else:
            self.setFixedWidth(200)

    def get_dayu_position(self):
        """
        Get the placement of the MDrawer
        :return: str
        """
        return self._position

    dayu_position = Property(str, get_dayu_position, set_dayu_position)

    def left(self):
        """Set drawer's placement to left"""
        self.set_dayu_position(MDrawer.LeftPos)
        return self

    def right(self):
        """Set drawer's placement to right"""
        self.set_dayu_position(MDrawer.RightPos)
        return self

    def top(self):
        """Set drawer's placement to top"""
        self.set_dayu_position(MDrawer.TopPos)
        return self

    def bottom(self):
        """Set drawer's placement to bottom"""
        self.set_dayu_position(MDrawer.BottomPos)
        return self

    def show(self):
        self._set_proper_position()
        self._fade_int()
        return super(MDrawer, self).show()

    def closeEvent(self, event):
        if self._is_first_close:
            self._is_first_close = False
            self._close_timer.start()
            self._fade_out()
            event.ignore()
        else:
            event.accept()
Beispiel #2
0
class MMessage(QWidget):
    """
    Display global messages as feedback in response to user operations.
    """
    InfoType = 'info'
    SuccessType = 'success'
    WarningType = 'warning'
    ErrorType = 'error'
    LoadingType = 'loading'

    default_config = {'duration': 2, 'top': 24}

    sig_closed = Signal()

    def __init__(self,
                 text,
                 duration=None,
                 dayu_type=None,
                 closable=False,
                 parent=None):
        super(MMessage, self).__init__(parent)
        self.setObjectName('message')
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog
                            | Qt.WA_TranslucentBackground
                            | Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_StyledBackground)

        if dayu_type == MMessage.LoadingType:
            _icon_label = MLoading.tiny()
        else:
            _icon_label = MAvatar.tiny()
            current_type = dayu_type or MMessage.InfoType
            _icon_label.set_dayu_image(
                MPixmap('{}_fill.svg'.format(current_type),
                        vars(dayu_theme).get(current_type + '_color')))

        self._content_label = MLabel(parent=self)
        # self._content_label.set_elide_mode(Qt.ElideMiddle)
        self._content_label.setText(text)

        self._close_button = MToolButton(
            parent=self).icon_only().svg('close_line.svg').tiny()
        self._close_button.clicked.connect(self.close)
        self._close_button.setVisible(closable or False)

        self._main_lay = QHBoxLayout()
        self._main_lay.addWidget(_icon_label)
        self._main_lay.addWidget(self._content_label)
        self._main_lay.addStretch()
        self._main_lay.addWidget(self._close_button)
        self.setLayout(self._main_lay)

        _close_timer = QTimer(self)
        _close_timer.setSingleShot(True)
        _close_timer.timeout.connect(self.close)
        _close_timer.timeout.connect(self.sig_closed)
        _close_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000)

        _ani_timer = QTimer(self)
        _ani_timer.timeout.connect(self._fade_out)
        _ani_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000 - 300)

        _close_timer.start()
        _ani_timer.start()

        self._pos_ani = QPropertyAnimation(self)
        self._pos_ani.setTargetObject(self)
        self._pos_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._pos_ani.setDuration(300)
        self._pos_ani.setPropertyName('pos')

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName('windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(1.0)

        self._set_proper_position(parent)
        self._fade_int()

    def _fade_out(self):
        self._pos_ani.setDirection(QAbstractAnimation.Backward)
        self._pos_ani.start()
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._pos_ani.start()
        self._opacity_ani.start()

    def _set_proper_position(self, parent):
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft(
        ) if parent.parent() is None else parent.mapToGlobal(
            parent_geo.topLeft())
        offset = 0
        for child in parent.children():
            if isinstance(child, MMessage) and child.isVisible():
                offset = max(offset, child.y())
        base = pos.y() + MMessage.default_config.get('top')
        target_x = pos.x() + parent_geo.width() / 2 - 100
        target_y = (offset + 50) if offset else base
        self._pos_ani.setStartValue(QPoint(target_x, target_y - 40))
        self._pos_ani.setEndValue(QPoint(target_x, target_y))

    @classmethod
    def info(cls, text, parent, duration=None, closable=None):
        """Show a normal message"""
        inst = cls(text,
                   dayu_type=MMessage.InfoType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def success(cls, text, parent, duration=None, closable=None):
        """Show a success message"""
        inst = cls(text,
                   dayu_type=MMessage.SuccessType,
                   duration=duration,
                   closable=closable,
                   parent=parent)

        inst.show()
        return inst

    @classmethod
    def warning(cls, text, parent, duration=None, closable=None):
        """Show a warning message"""
        inst = cls(text,
                   dayu_type=MMessage.WarningType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def error(cls, text, parent, duration=None, closable=None):
        """Show an error message"""
        inst = cls(text,
                   dayu_type=MMessage.ErrorType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def loading(cls, text, parent):
        """Show a message with loading animation"""
        inst = cls(text, dayu_type=MMessage.LoadingType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def config(cls, duration=None, top=None):
        """
        Config the global MMessage duration and top setting.
        :param duration: int (unit is second)
        :param top: int (unit is px)
        :return: None
        """
        if duration is not None:
            cls.default_config['duration'] = duration
        if top is not None:
            cls.default_config['top'] = top
Beispiel #3
0
class MToast(QWidget):
    """
    MToast
    A Phone style message.
    """
    InfoType = 'info'
    SuccessType = 'success'
    WarningType = 'warning'
    ErrorType = 'error'
    LoadingType = 'loading'

    default_config = {
        'duration': 2,
    }

    sig_closed = Signal()

    def __init__(self, text, duration=None, dayu_type=None, parent=None):
        super(MToast, self).__init__(parent)
        self.setWindowFlags(
            Qt.FramelessWindowHint | Qt.Dialog | Qt.WA_TranslucentBackground | Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_StyledBackground)

        _icon_lay = QHBoxLayout()
        _icon_lay.addStretch()

        if dayu_type == MToast.LoadingType:
            _icon_lay.addWidget(MLoading(size=dayu_theme.huge, color=dayu_theme.text_color_inverse))
        else:
            _icon_label = MAvatar()
            _icon_label.set_dayu_size(60)
            _icon_label.set_dayu_image(MPixmap('{}_line.svg'.format(dayu_type or MToast.InfoType),
                                               dayu_theme.text_color_inverse))
            _icon_lay.addWidget(_icon_label)
        _icon_lay.addStretch()

        _content_label = MLabel()
        _content_label.setText(text)
        _content_label.setAlignment(Qt.AlignCenter)

        _main_lay = QVBoxLayout()
        _main_lay.setContentsMargins(0, 0, 0, 0)
        _main_lay.addStretch()
        _main_lay.addLayout(_icon_lay)
        _main_lay.addSpacing(10)
        _main_lay.addWidget(_content_label)
        _main_lay.addStretch()
        self.setLayout(_main_lay)
        self.setFixedSize(QSize(120, 120))

        _close_timer = QTimer(self)
        _close_timer.setSingleShot(True)
        _close_timer.timeout.connect(self.close)
        _close_timer.timeout.connect(self.sig_closed)
        _close_timer.setInterval((duration or self.default_config.get('duration')) * 1000)

        _ani_timer = QTimer(self)
        _ani_timer.timeout.connect(self._fade_out)
        _ani_timer.setInterval((duration or self.default_config.get('duration')) * 1000 - 300)

        _close_timer.start()
        _ani_timer.start()

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName('windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(0.9)

        self._get_center_position(parent)
        self._fade_int()

    def _fade_out(self):
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._opacity_ani.start()

    def _get_center_position(self, parent):
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft() \
            if parent.parent() is None else parent.mapToGlobal(parent_geo.topLeft())
        offset = 0
        for child in parent.children():
            if isinstance(child, MToast) and child.isVisible():
                offset = max(offset, child.y())
        target_x = pos.x() + parent_geo.width() / 2 - self.width() / 2
        target_y = pos.y() + parent_geo.height() / 2 - self.height() / 2
        self.setProperty('pos', QPoint(target_x, target_y))

    @classmethod
    def info(cls, text, parent, duration=None):
        """Show a normal toast message"""
        inst = cls(text, duration=duration, dayu_type=MToast.InfoType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def success(cls, text, parent, duration=None):
        """Show a success toast message"""
        inst = cls(text, duration=duration, dayu_type=MToast.SuccessType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def warning(cls, text, parent, duration=None):
        """Show a warning toast message"""
        inst = cls(text, duration=duration, dayu_type=MToast.WarningType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def error(cls, text, parent, duration=None):
        """Show an error toast message"""
        inst = cls(text, duration=duration, dayu_type=MToast.ErrorType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def loading(cls, text, parent):
        """Show a toast message with loading animation"""
        inst = cls(text, dayu_type=MToast.LoadingType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def config(cls, duration):
        """
        Config the global MToast duration setting.
        :param duration: int (unit is second)
        :return: None
        """
        if duration is not None:
            cls.default_config['duration'] = duration