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.
This commit is contained in:
Kovid Goyal 2019-10-17 11:39:10 +05:30
parent 10d5d8b6bc
commit 7369171e33
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 79 additions and 90 deletions

View File

@ -9,18 +9,21 @@
#include <QStyleOptionToolButton>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <algorithm>
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();
}

View File

@ -3,11 +3,12 @@
#include <QtWidgets/QWidget>
#include <QColor>
#include <QHash>
#include <QPainter>
/*!
/*!
\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<int,QString> 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);

View File

@ -6,9 +6,11 @@
%Import QtWidgets/QtWidgetsmod.sip
%ModuleHeaderCode
#include <QPainter>
int load_style(QHash<int,QString> 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<int,QString> 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);