Пример #1
0
class CircleLoading(base.BaseWidget, object):
    def __init__(self, size=None, color=None, speed=1, parent=None):
        super(CircleLoading, self).__init__(parent=parent)

        size = size or self.theme_default_size()
        self.setFixedSize(QSize(size, size))

        self._rotation = 0
        self._loading_pixmap = resources.pixmap(
            'loading', extension='svg', color=color
            or self.accent_color()).scaledToWidth(size,
                                                  Qt.SmoothTransformation)
        self._loading_anim = QPropertyAnimation()
        self._loading_anim.setTargetObject(self)
        self._loading_anim.setDuration(1000 * (1 / speed))
        self._loading_anim.setPropertyName('rotation')
        self._loading_anim.setStartValue(0)
        self._loading_anim.setEndValue(360)
        self._loading_anim.setLoopCount(-1)
        self._loading_anim.start()

    # ============================================================================================================
    # PROPERTIES
    # ============================================================================================================

    def _set_rotation(self, value):
        self._rotation = value
        self.update()

    def _get_rotation(self):
        return self._rotation

    rotation = Property(int, _get_rotation, _set_rotation)

    # ============================================================================================================
    # OVERRIDES
    # ============================================================================================================

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.SmoothPixmapTransform)
        painter.translate(self._loading_pixmap.width() / 2,
                          self._loading_pixmap.height() / 2)
        painter.rotate(self._rotation)
        painter.drawPixmap(-self._loading_pixmap.width() / 2,
                           -self._loading_pixmap.height() / 2,
                           self._loading_pixmap.width(),
                           self._loading_pixmap.height(), self._loading_pixmap)
        painter.end()

        return super(CircleLoading, self).paintEvent(event)

    # ============================================================================================================
    # BASE
    # ============================================================================================================

    def set_size(self, size):
        """
        Sets the size of the widget
        :param size: int
        """

        self.setFixedSize(QSize(size, size))
        self._loading_pixmap = self._loading_pixmap.scaledToWidth(
            size, Qt.SmoothTransformation)

    @classmethod
    def tiny(cls, color=None, parent=None):
        """
        Creates a circle loading widget with tiny size
        :param color:
        :param parent:
        :return:
        """

        loading_widget = cls(color=color, parent=parent)
        loading_theme = loading_widget.theme()
        loading_size = loading_theme.tiny if loading_theme else theme.Theme.Sizes.TINY
        loading_widget.set_size(loading_size)

        return loading_widget

    @classmethod
    def small(cls, color=None, parent=None):
        """
        Creates a circle loading widget with small size
        :param color:
        :param parent:
        :return:
        """

        loading_widget = cls(color=color)
        loading_theme = loading_widget.theme()
        loading_size = loading_theme.small if loading_theme else theme.Theme.Sizes.SMALL
        loading_widget.set_size(loading_size)

        return loading_widget

    @classmethod
    def medium(cls, color=None, parent=None):
        """
        Creates a circle loading widget with medium size
        :param color:
        :param parent:
        :return:
        """

        loading_widget = cls(color=color)
        loading_theme = loading_widget.theme()
        loading_size = loading_theme.medium if loading_theme else theme.Theme.Sizes.MEDIUM
        loading_widget.set_size(loading_size)

        return loading_widget

    @classmethod
    def large(cls, color=None, parent=None):
        """
        Creates a circle loading widget with large size
        :param color:
        :param parent:
        :return:
        """

        loading_widget = cls(color=color)
        loading_theme = loading_widget.theme()
        loading_size = loading_theme.large if loading_theme else theme.Theme.Sizes.LARGE
        loading_widget.set_size(loading_size)

        return loading_widget

    @classmethod
    def huge(cls, color=None, parent=None):
        """
        Creates a circle loading widget with huge size
        :param color:
        :param parent:
        :return:
        """

        loading_widget = cls(color=color)
        loading_theme = loading_widget.theme()
        loading_size = loading_theme.huge if loading_theme else theme.Theme.Sizes.HUGE
        loading_widget.set_size(loading_size)

        return loading_widget
Пример #2
0
class PopupMessage(base.BaseWidget, object):
    """
    Message that appears at the top of the window and shows feedback in response to user actions
    """

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

    closed = Signal()

    def __init__(self,
                 text,
                 duration=None,
                 theme_type=None,
                 closable=False,
                 parent=None):

        self._text = text
        self._duration = duration
        self._theme_type = theme_type
        self._closable = closable

        super(PopupMessage, self).__init__(parent=parent)

        self.setAttribute(Qt.WA_TranslucentBackground)

        close_timer = QTimer(self)
        close_timer.setSingleShot(True)
        close_timer.timeout.connect(self.close)
        close_timer.timeout.connect(self.closed)
        close_timer.setInterval(
            (duration or self.DEFAULT_CONFIG['duration']) * 1000)
        anim_timer = QTimer(self)
        anim_timer.timeout.connect(self._on_fade_out)
        anim_timer.setInterval((duration or self.DEFAULT_CONFIG['duration']) *
                               1000 - 300)
        close_timer.start()
        anim_timer.start()

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

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

        self._set_proper_position(parent)
        self._fade_in()

    # =================================================================================================================
    # OVERRIDES
    # =================================================================================================================

    def get_main_layout(self):
        main_layout = layouts.HorizontalLayout()

        return main_layout

    def ui(self):
        super(PopupMessage, self).ui()

        current_theme = self.theme()

        self.setObjectName('message')
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog
                            | Qt.WA_TranslucentBackground
                            | Qt.WA_DeleteOnClose)
        # self.setAttribute(Qt.WA_TranslucentBackground)

        if self._theme_type == MessageTypes.LOADING:
            icon_label = loading.CircleLoading.tiny(parent=self)
        else:
            icon_label = avatar.Avatar.tiny()
            current_type = self._theme_type or MessageTypes.INFO
            if current_theme:
                icon_label.image = resources.pixmap(
                    current_type,
                    color=getattr(current_theme,
                                  '{}_color'.format(current_type)))

        main_frame = QFrame(self)
        main_frame_layout = layouts.HorizontalLayout(spacing=5,
                                                     margins=(5, 5, 5, 5))
        main_frame.setLayout(main_frame_layout)
        self.main_layout.addWidget(main_frame)

        self._content_label = label.BaseLabel(parent=self)
        self._content_label.setText(self._text)

        self._close_btn = buttons.BaseToolButton(parent=self).image(
            'close', theme='window').icon_only().tiny()
        self._close_btn.setVisible(self._closable or False)

        main_frame_layout.addWidget(icon_label)
        main_frame_layout.addWidget(self._content_label)
        main_frame_layout.addStretch()
        main_frame_layout.addWidget(self._close_btn)

    def setup_signals(self):
        self._close_btn.clicked.connect(self.close)

    # =================================================================================================================
    # BASE
    # =================================================================================================================

    @classmethod
    def info(cls, text, parent, duration=None, closable=None):
        """
        Shows an info message
        :param text: str
        :param parent: QWidget
        :param duration: int
        :param closable: bool
        :return: PopupMessage
        """

        popup_message_inst = cls(text,
                                 theme_type=MessageTypes.INFO,
                                 duration=duration,
                                 closable=closable,
                                 parent=parent)
        popup_message_inst.show()

        return popup_message_inst

    @classmethod
    def success(cls, text, parent, duration=None, closable=None):
        """
        Shows a success message
        :param text: str
        :param parent: QWidget
        :param duration: int
        :param closable: bool
        :return: PopupMessage
        """

        popup_message_inst = cls(text,
                                 theme_type=MessageTypes.SUCCESS,
                                 duration=duration,
                                 closable=closable,
                                 parent=parent)
        popup_message_inst.show()

        return popup_message_inst

    @classmethod
    def warning(cls, text, parent, duration=None, closable=None):
        """
        Shows a warning message
        :param text: str
        :param parent: QWidget
        :param duration: int
        :param closable: bool
        :return: PopupMessage
        """

        popup_message_inst = cls(text,
                                 theme_type=MessageTypes.WARNING,
                                 duration=duration,
                                 closable=closable,
                                 parent=parent)
        popup_message_inst.show()

        return popup_message_inst

    @classmethod
    def error(cls, text, parent, duration=None, closable=None):
        """
        Shows an error message
        :param text: str
        :param parent: QWidget
        :param duration: int
        :param closable: bool
        :return: PopupMessage
        """

        popup_message_inst = cls(text,
                                 theme_type=MessageTypes.ERROR,
                                 duration=duration,
                                 closable=closable,
                                 parent=parent)
        popup_message_inst.show()

        return popup_message_inst

    @classmethod
    def loading(cls, text, parent, duration=None, closable=None):
        """
        Shows a loading message
        :param text: str
        :param parent: QWidget
        :param duration: int
        :param closable: bool
        :return: PopupMessage
        """

        popup_message_inst = cls(text,
                                 theme_type=MessageTypes.LOADING,
                                 duration=duration,
                                 closable=closable,
                                 parent=parent)
        popup_message_inst.show()

        return popup_message_inst

    @classmethod
    def config(cls, duration=None, top=None):
        """
        Configures global PopupMesage duration and top setting
        :param duration: int (seconds)
        :param top: int (px)
        """

        if duration is not None:
            cls.DEFAULT_CONFIG['duration'] = duration
        if top is not None:
            cls.DEFAULT_CONFIG['top'] = top

    # =================================================================================================================
    # INTERNAL
    # =================================================================================================================

    def _fade_out(self):
        self._pos_anim.setDirection(QAbstractAnimation.Backward)
        self._pos_anim.start()
        self._opacity_anim.setDirection(QAbstractAnimation.Backward)
        self._opacity_anim.start()

    def _fade_in(self):
        self._pos_anim.start()
        self._opacity_anim.start()

    def _set_proper_position(self, parent):
        parent_parent = parent.parent()
        dcc_win = dcc.get_main_window()
        if dcc_win:
            dcc_window = parent_parent == dcc_win or parent_parent.objectName(
            ) == dcc_win.objectName()
        else:
            dcc_window = None
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft() if dcc_window else parent.mapToGlobal(
            parent_geo.topLeft())
        # 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, PopupMessage) and child.isVisible():
                offset = max(offset, child.y())
        base_pos = pos.y() + PopupMessage.DEFAULT_CONFIG.get('top')
        target_x = pos.x() + parent_geo.width() / 2 - 100
        target_y = (offset + 50) if offset else base_pos
        self._pos_anim.setStartValue(QPoint(target_x, target_y - 40))
        self._pos_anim.setEndValue(QPoint(target_x, target_y))

    # =================================================================================================================
    # CALLBACK
    # =================================================================================================================

    def _on_fade_out(self):
        self._fade_out()
Пример #3
0
class BaseToast(base.BaseWidget, object):
    class ToastTypes(object):
        INFO = 'info'
        SUCCESS = 'success'
        WARNING = 'warning'
        ERROR = 'error'
        LOADING = 'loading'

    DEFAULT_CONFIG = {'duration': 2}

    toastClosed = Signal()

    def __init__(self, text, duration=None, toast_type=None, parent=None):
        self._text = text
        self._duration = duration
        self._toast_type = toast_type
        self._parent = parent
        super(BaseToast, self).__init__(parent=parent)

    def get_main_layout(self):
        main_layout = layouts.VerticalLayout(margins=(0, 0, 0, 0))

        return main_layout

    def ui(self):
        super(BaseToast, self).ui()

        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog
                            | Qt.WA_TranslucentBackground
                            | Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_StyledBackground)
        self.setFixedSize(QSize(120, 120))

        icon_layout = layouts.HorizontalLayout()
        icon_layout.addStretch()

        widget_theme = self.theme()

        if self._toast_type == self.ToastTypes.LOADING:
            icon_layout.addWidget(
                loading.CircleLoading(size=widget_theme.huge,
                                      color=widget_theme.text_color_inverse))
        else:
            icon_label = avatar.Avatar()
            icon_label.theme_size = 60
            icon_label.image = resources.pixmap(
                self._toast_type or self.ToastTypes.INFO,
                color=widget_theme.text_color_inverse)
            icon_layout.addWidget(icon_label)
        icon_layout.addStretch()

        content_label = label.BaseLabel()
        content_label.setText(self._text or '')
        content_label.setAlignment(Qt.AlignCenter)

        self.main_layout.addStretch()
        self.main_layout.addLayout(icon_layout)
        self.main_layout.addSpacing(10)
        self.main_layout.addWidget(content_label)
        self.main_layout.addStretch()

        close_timer = QTimer(self)
        close_timer.setSingleShot(True)
        close_timer.timeout.connect(self.close)
        close_timer.timeout.connect(self.toastClosed.emit)
        close_timer.setInterval(
            (self._duration or self.DEFAULT_CONFIG.get('duration', 2)) * 1000)

        anim_timer = QTimer(self)
        anim_timer.timeout.connect(self._fade_out)
        anim_timer.setInterval(
            (self._duration or self.DEFAULT_CONFIG.get('duration', 2)) * 1000 -
            300)

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

        close_timer.start()
        anim_timer.start()

        self._get_center_position(self._parent)
        self._fade_in()

    @classmethod
    def info(cls, text, parent, duration=None):
        inst = cls(text,
                   duration=duration,
                   toast_type=cls.ToastTypes.INFO,
                   parent=parent)
        inst.show()

        return inst

    @classmethod
    def success(cls, text, parent, duration=None):
        inst = cls(text,
                   duration=duration,
                   toast_type=cls.ToastTypes.SUCCESS,
                   parent=parent)
        inst.show()

        return inst

    @classmethod
    def warning(cls, text, parent, duration=None):
        inst = cls(text,
                   duration=duration,
                   toast_type=cls.ToastTypes.WARNING,
                   parent=parent)
        inst.show()

        return inst

    @classmethod
    def error(cls, text, parent, duration=None):
        inst = cls(text,
                   duration=duration,
                   toast_type=cls.ToastTypes.ERROR,
                   parent=parent)
        inst.show()

        return inst

    @classmethod
    def loading(cls, text, parent, duration=None):
        inst = cls(text,
                   duration=duration,
                   toast_type=cls.ToastTypes.LOADING,
                   parent=parent)
        inst.show()

        return inst

    @classmethod
    def config(cls, duration):
        if duration is not None:
            cls.DEFAULT_CONFIG['duration'] = duration

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

    def _fade_in(self):
        self._opacity_anim.start()

    def _get_center_position(self, parent):
        parent_parent = parent.parent()
        dcc_win = dcc.get_main_window()
        if dcc_win:
            dcc_window = parent_parent == dcc_win or parent_parent.objectName(
            ) == dcc_win.objectName()
        else:
            dcc_window = None
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft() if dcc_window else parent.mapToGlobal(
            parent_geo.topLeft())
        offset = 0
        for child in parent.children():
            if isinstance(child, BaseToast) 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))
Пример #4
0
class SliderPanel(base.BaseWidget, object):
    """
    Panel that slides in from the edge of the window
    """

    closed = Signal()
    closeButtonClicked = Signal()

    def __init__(self,
                 title,
                 position=SliderPanelPositions.RIGHT,
                 closable=True,
                 parent=None):

        self._title = title
        self._position = position
        self._closable = closable
        self._is_first_close = True

        super(SliderPanel, self).__init__(parent)

        self.setObjectName('sliderPanel')
        self.setWindowFlags(Qt.Popup)
        self.setAttribute(Qt.WA_StyledBackground)

        self._close_timer = QTimer(self)
        self._close_timer.setInterval(300)
        self._close_timer.setSingleShot(True)
        self._close_timer.timeout.connect(self.close)
        self._close_timer.timeout.connect(self.closed.emit)

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

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

    # =================================================================================================================
    # PROPERTIES
    # =================================================================================================================

    @property
    def position(self):
        """
        Returns the placement of the panel in parent window
        :return: str
        """

        return self._position

    @position.setter
    def position(self, value):
        """
        Sets the position of the panel in parent window ('top', 'right', 'bottom' or 'left').
        :param value: str
        """

        self._position = value
        if value in [SliderPanelPositions.BOTTOM, SliderPanelPositions.TOP]:
            self.setFixedHeight(200)
        else:
            self.setFixedWidth(200)

    # =================================================================================================================
    # OVERRIDES
    # =================================================================================================================

    def ui(self):
        super(SliderPanel, self).ui()

        self._title_label = label.BaseLabel(parent=self).h4()
        self._title_label.setText(self._title)

        self._close_btn = buttons.BaseToolButton(
            parent=self).icon_only().image('close', theme='window').small()
        self._close_btn.setVisible(self._closable or False)

        title_layout = layouts.HorizontalLayout()
        title_layout.addWidget(self._title_label)
        title_layout.addStretch()
        title_layout.addWidget(self._close_btn)

        self._button_layout = layouts.HorizontalLayout()
        self._button_layout.addStretch()

        self._scroll_area = QScrollArea()
        self.main_layout.addLayout(title_layout)
        self.main_layout.addWidget(dividers.Divider())
        self.main_layout.addWidget(self._scroll_area)
        self.main_layout.addWidget(dividers.Divider())
        self.main_layout.addLayout(self._button_layout)

    def setup_signals(self):
        self._close_btn.clicked.connect(self.close)
        self._close_btn.clicked.connect(self.closeButtonClicked.emit)

    def show(self):
        self._update_position()
        self._fade_in()

        return super(SliderPanel, self).show()

    def closeEvent(self, event):
        if self._is_first_close:
            self._is_first_close = False
            self._close_timer.stop()
            self._fade_out()
            self.closed.emit()
            event.ignore()
        else:
            event.accept()

    # =================================================================================================================
    # BASE
    # =================================================================================================================

    def set_widget(self, widget):
        """
        Sets the widget that will be contained inside the panel
        :param widget: QWidget
        """

        self._scroll_area.setWidget(widget)

    def add_button(self, button):
        """
        Adds a new button to the bottom part of the panel
        :param button: QPushButton
        """

        self._button_layout.addWidget(button)

    def left(self):
        """
        Sets the panel's placement to left
        :return: SliderPanel
        """

        self.position = SliderPanelPositions.LEFT

        return self

    def right(self):
        """
        Sets the panel's placement to right
        :return: SliderPanel
        """

        self.position = SliderPanelPositions.RIGHT

        return self

    def top(self):
        """
        Sets the panel's placement to top
        :return: SliderPanel
        """

        self.position = SliderPanelPositions.TOP

        return self

    def bottom(self):
        """
        Sets the panel's placement to bottom
        :return: SliderPanel
        """

        self.position = SliderPanelPositions.BOTTOM

        return self

    # =================================================================================================================
    # INTERNAL
    # =================================================================================================================

    def _fade_in(self):
        """
        Internal function that fades in the panel
        """

        self._pos_anim.start()
        self._opacity_anim.start()

    def _fade_out(self):
        """
        Internal function that fades out the panel
        """

        self._pos_anim.setDirection(QAbstractAnimation.Backward)
        self._pos_anim.start()
        self._opacity_anim.setDirection(QAbstractAnimation.Backward)
        self._opacity_anim.start()

    def _update_position(self):
        """
        Internal function that makes sure that panel is positioned in the proper place
        """

        parent = self.parent()
        parent_parent = parent.parent()
        dcc_win = dcc.get_main_window()
        dcc_window = parent_parent == dcc_win
        if parent_parent and dcc_win:
            dcc_window = dcc_window or parent_parent.objectName(
            ) == dcc_win.objectName()
        parent_geo = parent.geometry()
        if self._position == SliderPanelPositions.LEFT:
            pos = parent_geo.topLeft() if dcc_window else parent.mapToGlobal(
                parent_geo.topLeft())
            target_x = pos.x()
            target_y = pos.y()
            self.setFixedHeight(parent_geo.height())
            self._pos_anim.setStartValue(
                QPoint(target_x - self.width(), target_y))
            self._pos_anim.setEndValue(QPoint(target_x, target_y))
        if self._position == SliderPanelPositions.RIGHT:
            pos = parent_geo.topRight() if dcc_window else parent.mapToGlobal(
                parent_geo.topRight())
            self.setFixedHeight(parent_geo.height())
            target_x = pos.x() - self.width()
            target_y = pos.y()
            self._pos_anim.setStartValue(
                QPoint(target_x + self.width(), target_y))
            self._pos_anim.setEndValue(QPoint(target_x, target_y))
        if self._position == SliderPanelPositions.TOP:
            pos = parent_geo.topLeft(
            ) if dcc_window or 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_anim.setStartValue(
                QPoint(target_x, target_y - self.height()))
            self._pos_anim.setEndValue(QPoint(target_x, target_y))
        if self._position == SliderPanelPositions.BOTTOM:
            pos = parent_geo.bottomLeft(
            ) if dcc_window else parent.mapToGlobal(parent_geo.bottomLeft())
            self.setFixedWidth(parent_geo.width())
            target_x = pos.x()
            target_y = pos.y() - self.height()
            self._pos_anim.setStartValue(
                QPoint(target_x, target_y + self.height()))
            self._pos_anim.setEndValue(QPoint(target_x, target_y))