Beispiel #1
0
    def _animate_expand(self, value):

        size_anim = QPropertyAnimation(self, 'geometry')
        geometry = self.geometry()
        width = geometry.width()
        x, y, _, _ = geometry.getCoords()
        size_start = QRect(x, y, width, int(not (value)) * 150)
        size_end = QRect(x, y, width, value * 150)
        size_anim.setStartValue(size_start)
        size_anim.setEndValue(size_end)
        size_anim.setDuration(300)
        size_anim_curve = QEasingCurve()
        if value:
            size_anim_curve.setType(QEasingCurve.InQuad)
        else:
            size_anim_curve.setType(QEasingCurve.OutQuad)
        size_anim.setEasingCurve(size_anim_curve)

        # =================================================== Animation Sequence

        self._animation = QSequentialAnimationGroup()
        self._animation.addAnimation(size_anim)
        size_anim.valueChanged.connect(self._force_resize)
        if not value:
            self._animation.finished.connect(self.delete_widget)
        self._animation.start(QAbstractAnimation.DeleteWhenStopped)
Beispiel #2
0
    def __init__(self, parent=None):
        super(JoyPad, self).__init__(parent=parent)

        self._x = 0
        self._y = 0
        self._bounds = QRectF()
        self._knop_bounds = QRectF()
        self._last_pos = QPoint()
        self._knop_pressed = False

        self._return_animation = QParallelAnimationGroup(self)
        self._x_anim = QPropertyAnimation(self, 'x')
        self._y_anim = QPropertyAnimation(self, 'y')
        self._alignment = Qt.AlignTop | Qt.AlignLeft

        self._x_anim.setEndValue(0.0)
        self._x_anim.setDuration(400)
        self._x_anim.setEasingCurve(QEasingCurve.OutSine)

        self._y_anim.setEndValue(0.0)
        self._y_anim.setDuration(400)
        self._y_anim.setEasingCurve(QEasingCurve.OutSine)

        self._return_animation.addAnimation(self._x_anim)
        self._return_animation.addAnimation(self._y_anim)
Beispiel #3
0
    def __init__(self, target, duration, parent=None):
        super(OpacityEffect, self).__init__(parent=parent)

        self._target = target
        self._duration = duration
        self._animation = QPropertyAnimation(self._target, "opacity")
        self._animation.setStartValue(0.0)
        self._animation.setEndValue(0.0)
Beispiel #4
0
    def __init__(self, title='', animation_duration=300, parent=None):
        super(ExpandableLine, self).__init__(parent=parent)

        self._animation_duration = animation_duration

        base_layout = layouts.GridLayout(margins=(0, 0, 0, 0))
        base_layout.setVerticalSpacing(0)
        self.setLayout(base_layout)

        self.expand_btn = QToolButton()
        self.expand_btn.setText(str(title))
        self.expand_btn.setStyleSheet('QToolButton { border : none; }')
        self.expand_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.expand_btn.setArrowType(Qt.ArrowType.RightArrow)
        self.expand_btn.setCheckable(True)
        self.expand_btn.setChecked(True)

        header_line = QFrame()
        header_line.setFrameShape(QFrame.HLine)
        header_line.setFrameShadow(QFrame.Sunken)
        header_line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)

        self.content_area = QScrollArea()
        self.content_area.setStyleSheet('QScrollArea { border: none;}')
        self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.content_area.setMaximumHeight(0)
        self.content_area.setMinimumHeight(0)

        self.toggle_anim = QParallelAnimationGroup()
        self.toggle_anim.addAnimation(QPropertyAnimation(self, 'minimumHeight'))
        self.toggle_anim.addAnimation(QPropertyAnimation(self, 'maximumHeight'))
        self.toggle_anim.addAnimation(QPropertyAnimation(self.content_area, 'maximumHeight'))

        row = 0
        base_layout.addWidget(self.expand_btn, row, 0, 1, 1, Qt.AlignLeft)
        base_layout.addWidget(header_line, row, 2, 1, 1)
        row += 1
        base_layout.addWidget(self.content_area, row, 0, 1, 3)

        def expand_view(checked):
            arrow_type = Qt.DownArrow if checked else Qt.RightArrow
            direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
            self.expand_btn.setArrowType(arrow_type)
            self.toggle_anim.setDirection(direction)
            self.toggle_anim.start()

        # === SIGNALS === #
        self.expand_btn.toggled.connect(expand_view)

        expand_view(True)
Beispiel #5
0
    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()
Beispiel #6
0
    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()
Beispiel #7
0
    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)
Beispiel #8
0
class OpacityEffect(QGraphicsEffect, object):
    def __init__(self, target, duration, parent=None):
        super(OpacityEffect, self).__init__(parent=parent)

        self._target = target
        self._duration = duration
        self._animation = QPropertyAnimation(self._target, "opacity")
        self._animation.setStartValue(0.0)
        self._animation.setEndValue(0.0)

    def get_duration(self):
        return self._duration

    def set_duration(self, value):
        self._duration = value

    def get_target(self):
        return self._target

    def set_target(self, value):
        self._target = value

    def get_animation(self):
        return self._animation

    duration = property(get_duration, set_duration)
    target = property(get_target, set_target)
    animation = property(get_animation)

    def fade_in_out(self):
        """
        Executes the animation
        """

        self._animation.stop()
        self._animation.setDuration(self._duration)
        self._animation.setEasingCurve(QEasingCurve.InOutQuad)
        self._animation.setStartValue(0.0)
        self._animation.setEndValue(0.0)
        self._animation.setKeyValueAt(0.3, 1.0)
        self._animation.setKeyValueAt(0.6, 1.0)
        self._animation.start()

    # region Functions
    def fade_in(self):
        """
        Fade in the opacity property of the effect target
        """

        self._animation.stop()
        self._animation.setEasingCurve(QEasingCurve.InOutQuad)
        self._animation.setDuration(self._duration)
        self._animation.setStartValue(0)
        self._animation.setEndValue(1)
        self._animation.start()

    def fade_out(self):
        """
        Fade out the opacity property of the effect target
        """

        self._animation.stop()
        self._animation.setEasingCurve(QEasingCurve.InOutQuad)
        self._animation.setDuration(self._duration)
        self._animation.setStartValue(1)
        self._animation.setEndValue(0)
        self._animation.start()
Beispiel #9
0
def property_animation(start=[0, 0], end=[30, 0], duration=300, object=None, property='iconSize', on_finished=None):
    """
    Functions that returns a ready to use QPropertyAnimation
    :param start: int, animation start value
    :param end: int, animation end value
    :param duration: int, duration of the effect
    :param object: variant, QDialog || QMainWindow
    :param property: str, object property to animate
    :param on_finished: variant, function to call when the animation is finished
    :return: QPropertyAnimation
    """

    animation = QPropertyAnimation(object, property, object)
    anim_curve = QEasingCurve()
    anim_curve.setType(QEasingCurve.InOutQuint)
    animation.setEasingCurve(anim_curve)
    animation.setDuration(duration)
    animation.setStartValue(start)
    animation.setEndValue(end)
    animation.start()

    return animation
Beispiel #10
0
def fade_animation(start=0, end=1, duration=300, object=None, on_finished=None):
    """
    Fade animation for widgets
     :param start: int, animation start value
    :param end: int, animation end value
    :param duration: int, duration of the effect
    :param object: variant, QDialog || QMainWindow
    :param on_finished: variant, function to call when the animation is finished
    :return: QPropertyAnimation
    """

    anim_curve = QEasingCurve()
    anim_curve.setType(QEasingCurve.OutQuint)

    if start == 'current':
        start = object.opacity()
    if end == 'current':
        end = object.opacity()

    animation = QPropertyAnimation(object, b'opacity', object)
    animation.setEasingCurve(anim_curve)
    animation.setDuration(duration)
    animation.setStartValue(start)
    animation.setEndValue(end)
    animation.start()

    if on_finished:
        animation.finished.connect(on_finished)
Beispiel #11
0
def slide_window(start=-100, end=0, duration=300, object=None, on_finished=None):
    """
    Slide animation for windows
    :param start: int, animation start value
    :param end: int, animation end value
    :param duration: int, duration of the effect
    :param object: variant, QDialog || QMainWindow
    :param on_finished: variant, function to call when the animation is finished
    :return: QPropertyAnimation
    """

    pos = object.pos()
    animation = QPropertyAnimation(object, b'pos', object)
    animation.setDuration(duration)
    anim_curve = QEasingCurve()
    if start >= end:
        anim_curve.setType(QEasingCurve.OutExpo)
    else:
        anim_curve.setType(QEasingCurve.InOutExpo)
    animation.setEasingCurve(anim_curve)
    animation.setStartValue(QPoint(pos.x(), pos.y() + start))
    animation.setEndValue(QPoint(pos.x(), pos.y() + end))
    animation.start()

    if on_finished:
        animation.finished.connect(on_finished)

    return animation
Beispiel #12
0
def fade_out_widget(widget, duration=200, on_finished=None):
    """
    Fade out animation effect for widgets
    :param widget: QWidget, widget to apply effect
    :param duration: int, duration of the effect
    :param on_finished: variant, function to call when the animation is finished
    :return: QPropertyAnimation
    """

    effect = QGraphicsOpacityEffect(widget)
    widget.setGraphicsEffect(effect)
    animation = QPropertyAnimation(effect, b'opacity')
    animation.setDuration(duration)
    animation.setStartValue(1.0)
    animation.setEndValue(0.0)
    animation.setEasingCurve(QEasingCurve.InOutCubic)
    animation.start()

    if on_finished:
        animation.finished.connect(on_finished)

    widget._fade_out_ = animation

    return animation
Beispiel #13
0
    def slide_in_index(self, next, force=False):
        """
        Slides to the given widget index
        :param next: int, index of the widget to slide
        """

        now = self.currentIndex()
        if (self._active_state or next == now) and not force:
            return

        self._active_state = True
        width, height = self.frameRect().width(), self.frameRect().height()
        next %= self.count()
        if next > now:
            if self._vertical:
                offset_x, offset_y = 0, height
            else:
                offset_x, offset_y = width, 0
        else:
            if self._vertical:
                offset_x, offset_y = 0, -height
            else:
                offset_x, offset_y = -width, 0
        self.widget(next).setGeometry(0, 0, width, height)
        pnow, pnext = self.widget(now).pos(), self.widget(next).pos()
        self._point_now = pnow

        self.widget(next).move(pnext.x() + offset_x, pnext.y() + offset_y)
        self.widget(next).show()
        self.widget(next).raise_()
        self._current_widget = self.widget(next)

        anim_now = QPropertyAnimation(self.widget(now), b'pos')
        anim_now.setDuration(self._speed)
        anim_now.setStartValue(pnow)
        anim_now.setEndValue(QPoint(pnow.x() - offset_x, pnow.y() - offset_y))
        anim_now.setEasingCurve(self._animation_type)

        anim_next = QPropertyAnimation(self.widget(next), b'pos')
        anim_next.setDuration(self._speed)
        anim_next.setStartValue(
            QPoint(offset_x + pnext.x(), offset_y + pnext.y()))
        anim_next.setEndValue(pnext)
        anim_next.setEasingCurve(self._animation_type)

        self._anim_group = QParallelAnimationGroup()
        self._anim_group.addAnimation(anim_now)
        self._anim_group.addAnimation(anim_next)
        self._anim_group.finished.connect(self._animation_done_slot)
        self._anim_group.start()

        self._next = next
        self._now = now
Beispiel #14
0
    def animate_expand(self, value):

        size_animation = QPropertyAnimation(self, b'geometry')
        geometry = self.geometry()
        width = geometry.width()
        x, y, _, _ = geometry.getCoords()
        size_start = QRect(x, y, width, int(not value) * self.INTERP_HEIGHT)
        size_end = QRect(x, y, width, value * 150)
        size_animation.setStartValue(size_start)
        size_animation.setEndValue(size_end)
        size_animation.setDuration(200)
        size_anim_curve = QEasingCurve()
        size_anim_curve.setType(
            QEasingCurve.InQuad) if value else size_anim_curve.setType(
                QEasingCurve.OutQuad)
        size_animation.setEasingCurve(size_anim_curve)

        opacity_animation = QPropertyAnimation(self._main_widget_proxy,
                                               b'opacity')
        opacity_animation.setStartValue(not (value))
        opacity_animation.setEndValue(value)
        opacity_animation.setDuration(100)
        opacity_anim_curve = QEasingCurve()
        opacity_anim_curve.setType(
            QEasingCurve.InQuad) if value else opacity_anim_curve.setType(
                QEasingCurve.OutQuad)
        opacity_animation.setEasingCurve(opacity_anim_curve)

        # We must store the animation objects as a member variables. Otherwise the animation object could be deleted
        # once the function is completed. In that case, the animation will not work.
        self._animation = QSequentialAnimationGroup()
        if value:
            self._main_widget_proxy.setOpacity(0)
            self._animation.addAnimation(size_animation)
            self._animation.addAnimation(opacity_animation)
        else:
            self._main_widget_proxy.setOpacity(1)
            self._animation.addAnimation(opacity_animation)
            self._animation.addAnimation(size_animation)

        # When animating geometry property, the parent layout is not updated automatically.
        # We force the resize of the layout by calling a signal each time the size animation value changes.
        size_animation.valueChanged.connect(self._on_force_resize)
        self._animation.finished.connect(self._animation.clear)

        if not value:
            self._animation.finished.connect(self._on_delete_widget)

        self._animation.start(QAbstractAnimation.DeleteWhenStopped)
Beispiel #15
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()
Beispiel #16
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))
Beispiel #17
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
Beispiel #18
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))
Beispiel #19
0
    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()
Beispiel #20
0
class JoyPad(QWidget, object):

    xChanged = Signal(float)
    yChanged = Signal(float)

    def __init__(self, parent=None):
        super(JoyPad, self).__init__(parent=parent)

        self._x = 0
        self._y = 0
        self._bounds = QRectF()
        self._knop_bounds = QRectF()
        self._last_pos = QPoint()
        self._knop_pressed = False

        self._return_animation = QParallelAnimationGroup(self)
        self._x_anim = QPropertyAnimation(self, 'x')
        self._y_anim = QPropertyAnimation(self, 'y')
        self._alignment = Qt.AlignTop | Qt.AlignLeft

        self._x_anim.setEndValue(0.0)
        self._x_anim.setDuration(400)
        self._x_anim.setEasingCurve(QEasingCurve.OutSine)

        self._y_anim.setEndValue(0.0)
        self._y_anim.setDuration(400)
        self._y_anim.setEasingCurve(QEasingCurve.OutSine)

        self._return_animation.addAnimation(self._x_anim)
        self._return_animation.addAnimation(self._y_anim)

    # region Static Functions
    @staticmethod
    def constraint(value, min_value, max_value):
        return min_value if value < min_value else max_value if value > max_value else value

    # endregion

    # region Properties
    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = self.constraint(x, -1.0, 1.0)
        radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5
        self._knop_bounds.moveCenter(
            QPointF(self._bounds.center().x() + self._x * radius,
                    self._knop_bounds.center().y()))
        self.update()
        self.xChanged.emit(self._x)

    def get_y(self):
        return self._y

    def set_y(self, y):
        self._y = self.constraint(y, -1.0, 1.0)
        radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5
        self._knop_bounds.moveCenter(
            QPointF(self._knop_bounds.center().x(),
                    self._bounds.center().y() - self._y * radius))
        self.update()
        self.yChanged.emit(self._x)

    def get_alignment(self):
        return self._alignment

    def set_alignment(self, alignment):
        self._alignment = alignment

    x = property(get_x, set_x)
    y = property(get_y, set_y)
    alignment = property(get_alignment, set_alignment)

    # endregion

    # region Override Functions
    def resizeEvent(self, event):
        a = min(self.width(), self.height())
        top_left = QPointF()
        if self._alignment & Qt.AlignTop:
            top_left.setY(0)
        elif self._alignment & Qt.AlignVCenter:
            top_left.setY((self.height() - a) * 0.5)
        elif self._alignment & Qt.AlignBottom:
            top_left.setY(self.height() - a)

        if self._alignment & Qt.AlignLeft:
            top_left.setX(0)
        elif self._alignment & Qt.AlignHCenter:
            top_left.setX((self.width() - a) * 0.5)
        elif self._alignment & Qt.AlignRight:
            top_left.setX(self.width() - a)

        self._bounds = QRectF(top_left, QSize(a, a))
        self._knop_bounds.setWidth(a * 0.3)
        self._knop_bounds.setHeight(a * 0.3)

        radius = (self._bounds.width() - self._knop_bounds.height()) * 0.5
        self._knop_bounds.moveCenter(
            QPointF(self._bounds.center().x() + self._x * radius,
                    self._bounds.center().y() - self._y * radius))

    def mousePressEvent(self, event):
        if self._knop_bounds.contains(event.pos()):
            # self._return_animation.stop()
            self._last_pos = event.pos()
            self._knop_pressed = True

    def mouseReleaseEvent(self, event):
        self._knop_pressed = False
        self.x = 0.0
        self.y = 0.0
        # self._return_animation.start()

    def mouseMoveEvent(self, event):
        if not self._knop_pressed:
            return

        delta_pos = QPointF(event.pos() - self._last_pos)
        delta_pos += 0.5 * (QPointF(event.pos()) - self._knop_bounds.center())

        from_center_to_knop = self._knop_bounds.center(
        ) + delta_pos - self._bounds.center()
        radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5
        from_center_to_knop.setX(
            self.constraint(from_center_to_knop.x(), -radius, radius))
        from_center_to_knop.setY(
            self.constraint(from_center_to_knop.y(), -radius, radius))
        self._knop_bounds.moveCenter(from_center_to_knop +
                                     self._bounds.center())
        self._last_pos = event.pos()

        self.update()

        if radius == 0:
            return
        x = (self._knop_bounds.center().x() -
             self._bounds.center().x()) / radius
        y = (-self._knop_bounds.center().y() +
             self._bounds.center().y()) / radius

        if self._x != x:
            self._x = x
            self.xChanged.emit(self._x)

        if self._y != y:
            self._y = y
            self.yChanged.emit(self._y)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setRenderHint(QPainter.HighQualityAntialiasing)

        gradient = QRadialGradient(self._bounds.center(),
                                   self._bounds.width() * 0.5,
                                   self._bounds.center())
        gradient.setFocalRadius(self._bounds.width() * 0.3)
        gradient.setCenterRadius(self._bounds.width() * 0.7)
        gradient.setColorAt(0, Qt.white)
        gradient.setColorAt(1, Qt.lightGray)

        painter.setPen(QPen(QBrush(Qt.gray), self._bounds.width() * 0.005))
        painter.setBrush(QBrush(gradient))
        painter.drawEllipse(self._bounds)

        painter.setPen(QPen(QBrush(Qt.gray), self._bounds.width() * 0.005))
        painter.drawLine(
            QPointF(self._bounds.left(),
                    self._bounds.center().y()),
            QPointF(self._bounds.center().x() - self._bounds.width() * 0.35,
                    self._bounds.center().y()))
        painter.drawLine(
            QPointF(self._bounds.center().x() + self._bounds.width() * 0.35,
                    self._bounds.center().y()),
            QPointF(self._bounds.right(),
                    self._bounds.center().y()))
        painter.drawLine(
            QPointF(self._bounds.center().x(), self._bounds.top()),
            QPointF(self._bounds.center().x(),
                    self._bounds.center().y() - self._bounds.width() * 0.35))
        painter.drawLine(
            QPointF(self._bounds.center().x(),
                    self._bounds.center().y() + self._bounds.width() * 0.35),
            QPointF(self._bounds.center().x(), self._bounds.bottom()))

        if not self.isEnabled():
            return

        gradient = QRadialGradient(self._knop_bounds.center(),
                                   self._knop_bounds.width() * 0.5,
                                   self._knop_bounds.center())
        gradient.setFocalRadius(self._knop_bounds.width() * 0.2)
        gradient.setCenterRadius(self._knop_bounds.width() * 0.5)
        gradient.setColorAt(0, Qt.gray)
        gradient.setColorAt(1, Qt.darkGray)

        painter.setPen(QPen(QBrush(Qt.darkGray), self._bounds.width() * 0.005))
        painter.setBrush(QBrush(gradient))
        painter.drawEllipse(self._knop_bounds)

        # endregion

    # region Public Functions
    def add_x_animation(self):
        # Abort if the animation is already added
        if self._x_anim.parent() == self._return_animation:
            return

        self._return_animation.addAnimation(self._x_anim)

    def remove_x_animation(self):
        # Abort if the animation is already removed
        if self._x_anim.parent() != self._return_animation:
            return

        self._return_animation.removeAnimation(self._x_anim)

        # Take ownership of the animation (parent is 0 after removeAnimation())
        self._x_anim.setParent(self)

    def add_y_animation(self):
        # Abort if the animation is already added
        if self._y_anim.parent() == self._return_animation:
            return

        self._return_animation.addAnimation(self._y_anim)

    def remove_y_animation(self):
        # Abort if the animation is already removed
        if self._y_anim.parent() != self._return_animation:
            return

        self._return_animation.removeAnimation(self._y_anim)

        # Take ownership of the animation (parent is 0 after removeAnimation())
        self._y_anim.setParent(self)