From 7369171e338cb421016891678799f22277f4e267 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 17 Oct 2019 11:39:10 +0530 Subject: [PATCH] Move spinner implementation into native code Should make it more performant and allow drawing without holding the GIL. Note that have not currently replaced the actual python implementation as that would break running from source. Will do that after the next release. --- .../progress_indicator/QProgressIndicator.cpp | 96 ++++++++++--------- .../progress_indicator/QProgressIndicator.h | 52 ++++------ .../progress_indicator/QProgressIndicator.sip | 21 ++-- 3 files changed, 79 insertions(+), 90 deletions(-) diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp index 6cee373cc7..9d5a9e19a8 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp @@ -9,18 +9,21 @@ #include #include #include +#include -QProgressIndicator::QProgressIndicator(QWidget* parent, int size) +QProgressIndicator::QProgressIndicator(QWidget* parent, int size, int interval) : QWidget(parent), m_angle(0), m_timerId(-1), - m_delay(80), - m_displaySize(size), - m_displayedWhenStopped(true), - m_color(Qt::black) + m_delay(interval), + m_displaySize(size, size), + m_dark(Qt::black), + m_light(Qt::white) { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setFocusPolicy(Qt::NoFocus); + m_dark = this->palette().color(QPalette::WindowText); + m_light = this->palette().color(QPalette::Window); } bool QProgressIndicator::isAnimated () const @@ -28,24 +31,22 @@ bool QProgressIndicator::isAnimated () const return (m_timerId != -1); } -void QProgressIndicator::setDisplayedWhenStopped(bool state) +void QProgressIndicator::setDisplaySize(QSize size) { - m_displayedWhenStopped = state; - - update(); + setSizeHint(size); } - -void QProgressIndicator::setDisplaySize(int size) +void QProgressIndicator::setSizeHint(int size) +{ + setSizeHint(QSize(size, size)); +} +void QProgressIndicator::setSizeHint(QSize size) { m_displaySize = size; update(); } -bool QProgressIndicator::isDisplayedWhenStopped() const -{ - return m_displayedWhenStopped; -} + void QProgressIndicator::startAnimation() { @@ -54,6 +55,7 @@ void QProgressIndicator::startAnimation() if (m_timerId == -1) m_timerId = startTimer(m_delay); } +void QProgressIndicator::start() { startAnimation(); } void QProgressIndicator::stopAnimation() { @@ -64,6 +66,7 @@ void QProgressIndicator::stopAnimation() update(); } +void QProgressIndicator::stop() { stopAnimation(); } void QProgressIndicator::setAnimationDelay(int delay) { @@ -76,16 +79,16 @@ void QProgressIndicator::setAnimationDelay(int delay) m_timerId = startTimer(m_delay); } -void QProgressIndicator::setColor(const QColor & color) +void QProgressIndicator::set_colors(const QColor & dark, const QColor & light) { - m_color = color; + m_dark = dark; m_light = light; update(); } QSize QProgressIndicator::sizeHint() const { - return QSize(m_displaySize, m_displaySize); + return m_displaySize; } int QProgressIndicator::heightForWidth(int w) const @@ -95,40 +98,15 @@ int QProgressIndicator::heightForWidth(int w) const void QProgressIndicator::timerEvent(QTimerEvent * /*event*/) { - m_angle = (m_angle+30)%360; + m_angle = (m_angle-2)%360; update(); } void QProgressIndicator::paintEvent(QPaintEvent * /*event*/) { - if (!m_displayedWhenStopped && !isAnimated()) - return; - - int width = qMin(this->width(), this->height()); - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - int outerRadius = (width-1)*0.5; - int innerRadius = (width-1)*0.5*0.38; - - int capsuleHeight = outerRadius - innerRadius; - int capsuleWidth = (width > 32 ) ? capsuleHeight *.23 : capsuleHeight *.35; - int capsuleRadius = capsuleWidth/2; - - for (int i=0; i<12; i++) - { - QColor color = m_color; - color.setAlphaF(1.0f - (i/12.0f)); - p.setPen(Qt::NoPen); - p.setBrush(color); - p.save(); - p.translate(rect().center()); - p.rotate(m_angle - i*30.0f); - p.drawRoundedRect(-capsuleWidth*0.5, -(innerRadius+capsuleHeight), capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius); - p.restore(); - } + QPainter painter(this); + draw_snake_spinner(painter, this->rect(), m_angle, m_light, m_dark); } static inline QByteArray detectDesktopEnvironment() @@ -282,3 +260,27 @@ class TouchMenuStyle: public QProxyStyle { void set_touch_menu_style(QWidget *widget, int margin) { widget->setStyle(new TouchMenuStyle(margin)); } + +void draw_snake_spinner(QPainter &painter, QRect rect, int angle, const QColor & light, const QColor & dark) { + painter.save(); + painter.setRenderHint(QPainter::Antialiasing); + if (rect.width() > rect.height()) { + int delta = (rect.width() - rect.height()) / 2; + rect = rect.adjusted(delta, 0, -delta, 0); + } else if (rect.height() > rect.width()) { + int delta = (rect.height() - rect.width()) / 2; + rect = rect.adjusted(0, delta, 0, -delta); + } + int disc_width = std::max(3, std::min(rect.width() / 10, 8)); + QRect drawing_rect(rect.x() + disc_width, rect.y() + disc_width, rect.width() - 2 * disc_width, rect.height() - 2 *disc_width); + int gap = 60; // degrees + QConicalGradient gradient(drawing_rect.center(), angle - gap / 2); + gradient.setColorAt((360 - gap/2)/360.0, light); + gradient.setColorAt(0, dark); + + QPen pen(QBrush(gradient), disc_width); + pen.setCapStyle(Qt::RoundCap); + painter.setPen(pen); + painter.drawArc(drawing_rect, angle * 16, (360 - gap) * 16); + painter.restore(); +} diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.h b/src/calibre/gui2/progress_indicator/QProgressIndicator.h index 95390cc6cf..a383a02aae 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.h +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.h @@ -3,11 +3,12 @@ #include #include #include +#include -/*! +/*! \class QProgressIndicator - \brief The QProgressIndicator class lets an application display a progress indicator to show that a lengthy task is under way. - + \brief The QProgressIndicator class lets an application display a progress indicator to show that a lengthy task is under way. + Progress indicators are indeterminate and do nothing more than spin to show that the application is busy. \sa QProgressBar */ @@ -15,11 +16,9 @@ class QProgressIndicator : public QWidget { Q_OBJECT Q_PROPERTY(int delay READ animationDelay WRITE setAnimationDelay) - Q_PROPERTY(bool displayedWhenStopped READ isDisplayedWhenStopped WRITE setDisplayedWhenStopped) - Q_PROPERTY(QColor color READ color WRITE setColor) - Q_PROPERTY(int displaySize READ displaySize WRITE setDisplaySize) + Q_PROPERTY(QSize displaySize READ displaySize WRITE setDisplaySize) public: - QProgressIndicator(QWidget* parent = 0, int size = 64); + QProgressIndicator(QWidget* parent = 0, int size = 64, int interval = 10); /*! Returns the delay between animation steps. \return The number of milliseconds between animation steps. By default, the animation delay is set to 80 milliseconds. @@ -33,65 +32,52 @@ public: */ bool isAnimated () const; - /*! Returns a Boolean value indicating whether the receiver shows itself even when it is not animating. - \return Return true if the progress indicator shows itself even when it is not animating. By default, it returns false. - \sa setDisplayedWhenStopped - */ - bool isDisplayedWhenStopped() const; - - /*! Returns the color of the component. - \sa setColor - */ - const QColor & color() const { return m_color; } - virtual QSize sizeHint() const; int heightForWidth(int w) const; - int displaySize() const { return m_displaySize; } + QSize displaySize() const { return m_displaySize; } public slots: /*! Starts the spin animation. \sa stopAnimation isAnimated */ void startAnimation(); + void start(); /*! Stops the spin animation. \sa startAnimation isAnimated */ void stopAnimation(); + void stop(); /*! Sets the delay between animation steps. Setting the \a delay to a value larger than 40 slows the animation, while setting the \a delay to a smaller value speeds it up. - \param delay The delay, in milliseconds. - \sa animationDelay + \param delay The delay, in milliseconds. + \sa animationDelay */ void setAnimationDelay(int delay); - /*! Sets whether the component hides itself when it is not animating. - \param state The animation state. Set false to hide the progress indicator when it is not animating; otherwise true. - \sa isDisplayedWhenStopped - */ - void setDisplayedWhenStopped(bool state); - /*! Sets the color of the components to the given color. \sa color */ - void setColor(const QColor & color); + void set_colors(const QColor & dark, const QColor & light); /*! Set the size of this widget (used by sizeHint) * \sa displaySize */ - void setDisplaySize(int size); + void setDisplaySize(QSize size); + void setSizeHint(int size); + void setSizeHint(QSize size); protected: - virtual void timerEvent(QTimerEvent * event); + virtual void timerEvent(QTimerEvent * event); virtual void paintEvent(QPaintEvent * event); private: int m_angle; int m_timerId; int m_delay; - int m_displaySize; - bool m_displayedWhenStopped; - QColor m_color; + QSize m_displaySize; + QColor m_dark, m_light; }; int load_style(QHash icon_map); void set_no_activate_on_click(QWidget *widget); void set_touch_menu_style(QWidget *widget, int margin=14); +void draw_snake_spinner(QPainter &painter, QRect rect, int angle, const QColor & light, const QColor & dark); diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip index 00596b6ad1..11e9bdcb7e 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip @@ -6,9 +6,11 @@ %Import QtWidgets/QtWidgetsmod.sip %ModuleHeaderCode +#include int load_style(QHash icon_map); void set_no_activate_on_click(QWidget *widget); void set_touch_menu_style(QWidget *widget, int margin); +void draw_snake_spinner(QPainter &painter, QRect rect, int angle, const QColor & light, const QColor & dark); %End class QProgressIndicator : QWidget { @@ -19,34 +21,32 @@ class QProgressIndicator : QWidget { public: - QProgressIndicator(QWidget *parent /TransferThis/ = 0, int size = 64); + QProgressIndicator(QWidget *parent /TransferThis/ = 0, int size = 64, int interval = 10); int animationDelay() const; bool isAnimated () const; - bool isDisplayedWhenStopped() const; - - const QColor & color() const; - virtual QSize sizeHint() const; int heightForWidth(int w) const; - int displaySize() const; + QSize displaySize() const; public slots: void startAnimation(); + void start(); void stopAnimation(); + void stop(); void setAnimationDelay(int delay); - void setDisplayedWhenStopped(bool state); + void set_colors(const QColor & dark, const QColor & light); - void setColor(const QColor & color); - - void setDisplaySize(int size); + void setDisplaySize(QSize size); + void setSizeHint(int size); + void setSizeHint(QSize size); protected: @@ -59,3 +59,4 @@ protected: int load_style(QHash icon_map); void set_no_activate_on_click(QWidget *widget); void set_touch_menu_style(QWidget *widget, int margin=20); +void draw_snake_spinner(QPainter &painter, QRect rect, int angle, const QColor & light, const QColor & dark);