Toggle Switch in Qt
Here is an example:
switch.h
:
#pragma once
#include <QtWidgets>
class Switch : public QAbstractButton {
Q_OBJECT
Q_PROPERTY(int offset READ offset WRITE setOffset)
Q_PROPERTY(QBrush brush READ brush WRITE setBrush)
public:
Switch(QWidget* parent = nullptr);
Switch(const QBrush& brush, QWidget* parent = nullptr);
QSize sizeHint() const override;
QBrush brush() const {
return _brush;
}
void setBrush(const QBrush &brsh) {
_brush = brsh;
}
int offset() const {
return _x;
}
void setOffset(int o) {
_x = o;
update();
}
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void enterEvent(QEvent*) override;
private:
bool _switch;
qreal _opacity;
int _x, _y, _height, _margin;
QBrush _thumb, _track, _brush;
QPropertyAnimation *_anim = nullptr;
};
switch.cpp
:
Switch::Switch(QWidget *parent) : QAbstractButton(parent),
_height(16),
_opacity(0.000),
_switch(false),
_margin(3),
_thumb("#d5d5d5"),
_anim(new QPropertyAnimation(this, "offset", this))
{
setOffset(_height / 2);
_y = _height / 2;
setBrush(QColor("#009688"));
}
Switch::Switch(const QBrush &brush, QWidget *parent) : QAbstractButton(parent),
_height(16),
_switch(false),
_opacity(0.000),
_margin(3),
_thumb("#d5d5d5"),
_anim(new QPropertyAnimation(this, "offset", this))
{
setOffset(_height / 2);
_y = _height / 2;
setBrush(brush);
}
void Switch::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.setPen(Qt::NoPen);
if (isEnabled()) {
p.setBrush(_switch ? brush() : Qt::black);
p.setOpacity(_switch ? 0.5 : 0.38);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawRoundedRect(QRect(_margin, _margin, width() - 2 * _margin, height() - 2 * _margin), 8.0, 8.0);
p.setBrush(_thumb);
p.setOpacity(1.0);
p.drawEllipse(QRectF(offset() - (_height / 2), _y - (_height / 2), height(), height()));
} else {
p.setBrush(Qt::black);
p.setOpacity(0.12);
p.drawRoundedRect(QRect(_margin, _margin, width() - 2 * _margin, height() - 2 * _margin), 8.0, 8.0);
p.setOpacity(1.0);
p.setBrush(QColor("#BDBDBD"));
p.drawEllipse(QRectF(offset() - (_height / 2), _y - (_height / 2), height(), height()));
}
}
void Switch::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() & Qt::LeftButton) {
_switch = _switch ? false : true;
_thumb = _switch ? _brush : QBrush("#d5d5d5");
if (_switch) {
_anim->setStartValue(_height / 2);
_anim->setEndValue(width() - _height);
_anim->setDuration(120);
_anim->start();
} else {
_anim->setStartValue(offset());
_anim->setEndValue(_height / 2);
_anim->setDuration(120);
_anim->start();
}
}
QAbstractButton::mouseReleaseEvent(e);
}
void Switch::enterEvent(QEvent *e) {
setCursor(Qt::PointingHandCursor);
QAbstractButton::enterEvent(e);
}
QSize Switch::sizeHint() const {
return QSize(2 * (_height + _margin), _height + 2 * _margin);
}
main.cpp
:
#include "switch.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QWidget *widget = new QWidget;
widget->setWindowFlags(Qt::FramelessWindowHint);
QHBoxLayout layout;
widget->setLayout(&layout);
Switch *_switch = new Switch;
Switch *_switch2 = new Switch;
_switch2->setDisabled(true);
layout.addWidget(_switch);
layout.addWidget(_switch2);
widget->show();
return a.exec();
}
Update Aug 20'18
New Material Switch Widget!
style.h
/*
* This is nearly complete Material design Switch widget implementation in qtwidgets module.
* More info: https://material.io/design/components/selection-controls.html#switches
* Copyright (C) 2018 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef STYLE_H
#define STYLE_H
#include <QtCore/qeasingcurve.h>
#define cyan500 QColor("#00bcd4")
#define gray50 QColor("#fafafa")
#define black QColor("#000000")
#define gray400 QColor("#bdbdbd")
Q_DECL_IMPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); // src/widgets/effects/qpixmapfilter.cpp
namespace Style {
using Type = QEasingCurve::Type;
struct Animation {
Animation() = default;
Animation(Type _easing, int _duration) :easing{ _easing }, duration{ _duration } {
}
Type easing;
int duration;
};
struct Switch {
Switch() :
height{ 36 },
font{ QFont("Roboto medium", 13) },
indicatorMargin{ QMargins(8, 8, 8, 8) },
thumbOnBrush{ cyan500 },
thumbOnOpacity{ 1 },
trackOnBrush{ cyan500 },
trackOnOpacity{ 0.5 },
thumbOffBrush{ gray50 },
thumbOffOpacity{ 1 },
trackOffBrush{ black },
trackOffOpacity{ 0.38 },
thumbDisabled{ gray400 },
thumbDisabledOpacity{ 1 },
trackDisabled{ black },
trackDisabledOpacity{ 0.12 },
textColor{ black },
disabledTextOpacity{ 0.26 },
thumbBrushAnimation{ Animation(Type::Linear, 150) },
trackBrushAnimation{ Animation(Type::Linear, 150) },
thumbPosAniamtion{ Animation(Type::InOutQuad, 150) } {
}
int height;
QFont font;
QMargins indicatorMargin;
QColor thumbOnBrush;
double thumbOnOpacity;
QColor trackOnBrush;
double trackOnOpacity;
QColor thumbOffBrush;
double thumbOffOpacity;
QColor trackOffBrush;
double trackOffOpacity;
QColor thumbDisabled;
double thumbDisabledOpacity;
QColor trackDisabled;
double trackDisabledOpacity;
QColor textColor;
double disabledTextOpacity;
Animation thumbBrushAnimation;
Animation trackBrushAnimation;
Animation thumbPosAniamtion;
};
inline QPixmap drawShadowEllipse(qreal radius, qreal elevation, const QColor& color) {
auto px = QPixmap(radius * 2, radius * 2);
px.fill(Qt::transparent);
{ // draw ellipes
QPainter p(&px);
p.setBrush(color);
p.setPen(Qt::NoPen);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawEllipse(QRectF(0, 0, px.size().width(), px.size().height()).center(), radius - elevation, radius - elevation);
}
QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied);
tmp.setDevicePixelRatio(px.devicePixelRatioF());
tmp.fill(0);
QPainter tmpPainter(&tmp);
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
tmpPainter.drawPixmap(QPointF(), px);
tmpPainter.end();
// blur the alpha channel
QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
blurred.setDevicePixelRatio(px.devicePixelRatioF());
blurred.fill(0);
{
QPainter blurPainter(&blurred);
qt_blurImage(&blurPainter, tmp, elevation * 4., true, false);
}
tmp = blurred;
return QPixmap::fromImage(tmp);
}
} // namespace Style
#endif // STYLE_H
switch.h
/*
* This is nearly complete Material design Switch widget implementation in qtwidgets module.
* More info: https://material.io/design/components/selection-controls.html#switches
* Copyright (C) 2018-2020 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef SWITCH_H
#define SWITCH_H
#include <QtWidgets>
#include "style.h"
class Animator final : public QVariantAnimation {
Q_OBJECT
Q_PROPERTY(QObject* targetObject READ targetObject WRITE setTargetObject)
public:
Animator(QObject* target, QObject* parent = nullptr);
~Animator() override;
QObject* targetObject() const;
void setTargetObject(QObject* target);
inline bool isRunning() const {
return state() == Running;
}
public slots:
void setup(int duration, QEasingCurve easing = QEasingCurve::Linear);
void interpolate(const QVariant& start, const QVariant& end);
void setCurrentValue(const QVariant&);
protected:
void updateCurrentValue(const QVariant& value) override final;
void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) override final;
private:
QPointer<QObject> target;
};
class SelectionControl : public QAbstractButton {
Q_OBJECT
public:
explicit SelectionControl(QWidget* parent = nullptr);
~SelectionControl() override;
Qt::CheckState checkState() const;
Q_SIGNALS:
void stateChanged(int);
protected:
void enterEvent(QEvent*) override;
void checkStateSet() override;
void nextCheckState() override;
virtual void toggle(Qt::CheckState state) = 0;
};
class Switch final : public SelectionControl {
Q_OBJECT
static constexpr auto CORNER_RADIUS = 8.0;
static constexpr auto THUMB_RADIUS = 14.5;
static constexpr auto SHADOW_ELEVATION = 2.0;
public:
explicit Switch(QWidget* parent = nullptr);
Switch(const QString& text, QWidget* parent = nullptr);
Switch(const QString& text, const QBrush&, QWidget* parent = nullptr);
~Switch() override;
QSize sizeHint() const override final;
protected:
void paintEvent(QPaintEvent*) override final;
void resizeEvent(QResizeEvent*) override final;
void toggle(Qt::CheckState) override final;
void init();
QRect indicatorRect();
QRect textRect();
static inline QColor colorFromOpacity(const QColor& c, qreal opacity) {
return QColor(c.red(), c.green(), c.blue(), qRound(opacity * 255.0));
}
static inline bool ltr(QWidget* w) {
if (nullptr != w)
return w->layoutDirection() == Qt::LeftToRight;
return false;
}
private:
Style::Switch style;
QPixmap shadowPixmap;
QPointer<Animator> thumbBrushAnimation;
QPointer<Animator> trackBrushAnimation;
QPointer<Animator> thumbPosAniamtion;
};
#endif // SWITCH_H
switch.cpp
/*
* This is nearly complete Material design Switch widget implementation in qtwidgets module.
* More info: https://material.io/design/components/selection-controls.html#switches
* Copyright (C) 2018-2020 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "switch.h"
Animator::Animator(QObject* target, QObject* parent) : QVariantAnimation(parent) {
setTargetObject(target);
}
Animator::~Animator() {
stop();
}
QObject* Animator::targetObject() const {
return target.data();
}
void Animator::setTargetObject(QObject* _target) {
if (target.data() == _target)
return;
if (isRunning()) {
qWarning("Animation::setTargetObject: you can't change the target of a running animation");
return;
}
target = _target;
}
void Animator::updateCurrentValue(const QVariant& value) {
Q_UNUSED(value);
if (!target.isNull()) {
auto update = QEvent(QEvent::StyleAnimationUpdate);
update.setAccepted(false);
QCoreApplication::sendEvent(target.data(), &update);
if (!update.isAccepted())
stop();
}
}
void Animator::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) {
if (target.isNull() && oldState == Stopped) {
qWarning("Animation::updateState: Changing state of an animation without target");
return;
}
QVariantAnimation::updateState(newState, oldState);
if (!endValue().isValid() && direction() == Forward) {
qWarning("Animation::updateState (%s): starting an animation without end value", targetObject()->metaObject()->className());
}
}
void Animator::setup(int duration, QEasingCurve easing) {
setDuration(duration);
setEasingCurve(easing);
}
void Animator::interpolate(const QVariant& _start, const QVariant& end) {
setStartValue(_start);
setEndValue(end);
start();
}
void Animator::setCurrentValue(const QVariant& value) {
setStartValue(value);
setEndValue(value);
updateCurrentValue(currentValue());
}
SelectionControl::SelectionControl(QWidget* parent) : QAbstractButton(parent) {
setObjectName("SelectionControl");
setCheckable(true);
}
SelectionControl::~SelectionControl() {
}
void SelectionControl::enterEvent(QEvent* e) {
setCursor(Qt::PointingHandCursor);
QAbstractButton::enterEvent(e);
}
Qt::CheckState SelectionControl::checkState() const {
return isChecked() ? Qt::Checked : Qt::Unchecked;
}
void SelectionControl::checkStateSet() {
const auto state = checkState();
emit stateChanged(state);
toggle(state);
}
void SelectionControl::nextCheckState() {
QAbstractButton::nextCheckState();
SelectionControl::checkStateSet();
}
void Switch::init() {
setFont(style.font);
setObjectName("Switch");
/* setup animations */
thumbBrushAnimation = new Animator{ this, this };
trackBrushAnimation = new Animator{ this, this };
thumbPosAniamtion = new Animator{ this, this };
thumbPosAniamtion->setup(style.thumbPosAniamtion.duration, style.thumbPosAniamtion.easing);
trackBrushAnimation->setup(style.trackBrushAnimation.duration, style.trackBrushAnimation.easing);
thumbBrushAnimation->setup(style.thumbBrushAnimation.duration, style.thumbBrushAnimation.easing);
/* set init values */
trackBrushAnimation->setStartValue(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity));
trackBrushAnimation->setEndValue(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity));
thumbBrushAnimation->setStartValue(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity));
thumbBrushAnimation->setEndValue(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity));
/* set standard palettes */
auto p = palette();
p.setColor(QPalette::Active, QPalette::ButtonText, style.textColor);
p.setColor(QPalette::Disabled, QPalette::ButtonText, style.textColor);
setPalette(p);
setSizePolicy(QSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed));
}
QRect Switch::indicatorRect() {
const auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right();
return ltr(this) ? QRect(0, 0, w, style.height) : QRect(width() - w, 0, w, style.height);
}
QRect Switch::textRect() {
const auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right();
return ltr(this) ? rect().marginsRemoved(QMargins(w, 0, 0, 0)) : rect().marginsRemoved(QMargins(0, 0, w, 0));
}
Switch::Switch(QWidget* parent) : SelectionControl(parent) {
init();
}
Switch::Switch(const QString& text, QWidget* parent) : Switch(parent) {
setText(text);
}
Switch::Switch(const QString& text, const QBrush& brush, QWidget* parent) : Switch(text, parent) {
style.thumbOnBrush = brush.color();
style.trackOnBrush = brush.color();
}
Switch::~Switch() {
}
QSize Switch::sizeHint() const {
auto h = style.height;
auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right() + fontMetrics().width(text());
return QSize(w, h);
}
void Switch::paintEvent(QPaintEvent*) {
/* for desktop usage we do not need Radial reaction */
QPainter p(this);
const auto _indicatorRect = indicatorRect();
const auto _textRect = textRect();
auto trackMargin = style.indicatorMargin;
trackMargin.setTop(trackMargin.top() + 2);
trackMargin.setBottom(trackMargin.bottom() + 2);
QRectF trackRect = _indicatorRect.marginsRemoved(trackMargin);
if (isEnabled()) {
p.setOpacity(1.0);
p.setPen(Qt::NoPen);
/* draw track */
p.setBrush(trackBrushAnimation->currentValue().value<QColor>());
p.setRenderHint(QPainter::Antialiasing, true);
p.drawRoundedRect(trackRect, CORNER_RADIUS, CORNER_RADIUS);
p.setRenderHint(QPainter::Antialiasing, false);
/* draw thumb */
trackRect.setX(trackRect.x() - trackMargin.left() - trackMargin.right() - 2 + thumbPosAniamtion->currentValue().toInt());
auto thumbRect = trackRect;
if (!shadowPixmap.isNull())
p.drawPixmap(thumbRect.center() - QPointF(THUMB_RADIUS, THUMB_RADIUS - 1.0), shadowPixmap);
p.setBrush(thumbBrushAnimation->currentValue().value<QColor>());
p.setRenderHint(QPainter::Antialiasing, true);
// qDebug() << thumbRect << thumbPosAniamtion->currentValue();
p.drawEllipse(thumbRect.center(), THUMB_RADIUS - SHADOW_ELEVATION - 1.0, THUMB_RADIUS - SHADOW_ELEVATION - 1.0);
p.setRenderHint(QPainter::Antialiasing, false);
/* draw text */
if (text().isEmpty())
return;
p.setOpacity(1.0);
p.setPen(palette().color(QPalette::Active, QPalette::ButtonText));
p.setFont(font());
p.drawText(_textRect, Qt::AlignLeft | Qt::AlignVCenter, text());
} else {
p.setOpacity(style.trackDisabledOpacity);
p.setPen(Qt::NoPen);
// draw track
p.setBrush(style.trackDisabled);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawRoundedRect(trackRect, CORNER_RADIUS, CORNER_RADIUS);
p.setRenderHint(QPainter::Antialiasing, false);
// draw thumb
p.setOpacity(1.0);
if (!isChecked())
trackRect.setX(trackRect.x() - trackMargin.left() - trackMargin.right() - 2);
else
trackRect.setX(trackRect.x() + trackMargin.left() + trackMargin.right() + 2);
auto thumbRect = trackRect;
if (!shadowPixmap.isNull())
p.drawPixmap(thumbRect.center() - QPointF(THUMB_RADIUS, THUMB_RADIUS - 1.0), shadowPixmap);
p.setOpacity(1.0);
p.setBrush(style.thumbDisabled);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawEllipse(thumbRect.center(), THUMB_RADIUS - SHADOW_ELEVATION - 1.0, THUMB_RADIUS - SHADOW_ELEVATION - 1.0);
/* draw text */
if (text().isEmpty())
return;
p.setOpacity(style.disabledTextOpacity);
p.setPen(palette().color(QPalette::Disabled, QPalette::ButtonText));
p.setFont(font());
p.drawText(_textRect, Qt::AlignLeft | Qt::AlignVCenter, text());
}
}
void Switch::resizeEvent(QResizeEvent* e) {
shadowPixmap = Style::drawShadowEllipse(THUMB_RADIUS, SHADOW_ELEVATION, QColor(0, 0, 0, 70));
SelectionControl::resizeEvent(e);
}
void Switch::toggle(Qt::CheckState state) {
if (state == Qt::Checked) {
const QVariant posEnd = (style.indicatorMargin.left() + style.indicatorMargin.right() + 2) * 2;
const QVariant thumbEnd = colorFromOpacity(style.thumbOnBrush, style.thumbOnOpacity);
const QVariant trackEnd = colorFromOpacity(style.trackOnBrush, style.trackOnOpacity);
if (!isVisible()) {
thumbPosAniamtion->setCurrentValue(posEnd);
thumbBrushAnimation->setCurrentValue(thumbEnd);
trackBrushAnimation->setCurrentValue(trackEnd);
} else {
thumbPosAniamtion->interpolate(0, posEnd);
thumbBrushAnimation->interpolate(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity), thumbEnd);
trackBrushAnimation->interpolate(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity), trackEnd);
}
} else { // Qt::Unchecked
const QVariant posEnd = 0;
const QVariant thumbEnd = colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity);
const QVariant trackEnd = colorFromOpacity(style.trackOffBrush, style.trackOffOpacity);
if (!isVisible()) {
thumbPosAniamtion->setCurrentValue(posEnd);
thumbBrushAnimation->setCurrentValue(thumbEnd);
trackBrushAnimation->setCurrentValue(trackEnd);
} else {
thumbPosAniamtion->interpolate(thumbPosAniamtion->currentValue().toInt(), posEnd);
thumbBrushAnimation->interpolate(colorFromOpacity(style.thumbOnBrush, style.thumbOnOpacity), thumbEnd);
trackBrushAnimation->interpolate(colorFromOpacity(style.trackOnBrush, style.trackOnOpacity), trackEnd);
}
}
}
main.cpp
#include "switch.h"
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
QWidget container;
QVBoxLayout mainLayout;
container.setLayout(&mainLayout);
Switch* switch1 = new Switch("SWITCH");
mainLayout.addWidget(switch1);
Switch* switch2 = new Switch("SWITCH");
mainLayout.addWidget(switch2);
switch2->setDisabled(true);
Switch* switch3 = new Switch("SWITCH");
mainLayout.addWidget(switch3);
switch3->setLayoutDirection(Qt::RightToLeft);
Switch* switch4 = new Switch("SWITCH");
mainLayout.addWidget(switch4);
switch4->setLayoutDirection(Qt::RightToLeft);
switch4->setChecked(true);
switch4->setDisabled(true);
QButtonGroup bg;
Switch* item1 = new Switch("ITEM1");
Switch* item2 = new Switch("ITEM2");
bg.addButton(item1);
bg.addButton(item2);
mainLayout.addWidget(item1);
mainLayout.addWidget(item2);
mainLayout.setMargin(100);
container.show();
return application.exec();
}
Result:
A few months ago I made an implementation whose visuals needed to be more consistent with common desktop styles (C++ and Python version available; the Python version was the prototype, i.e. they might work differently). Notice that the aesthetics is fully customized using paintEvent
. Do not expect different visuals depending on system.
C++ implementation
NOTE: don't forget the includes (which are not in my example).
Usage:
SwitchButton* sbtn = new SwitchButton(this); // Default style is Style::ONOFF
bool current = sbtn->value();
sbtn->setValue(!current);
(...).hpp
class SwitchButton : public QWidget
{
Q_OBJECT
Q_DISABLE_COPY(SwitchButton)
public:
enum Style
{
YESNO,
ONOFF,
BOOL,
EMPTY
};
public:
explicit SwitchButton(QWidget* parent = nullptr, Style style = Style::ONOFF);
~SwitchButton() override;
//-- QWidget methods
void mousePressEvent(QMouseEvent *) override;
void paintEvent(QPaintEvent* event) override;
void setEnabled(bool);
//-- Setters
void setDuration(int);
void setValue(bool);
//-- Getters
bool value() const;
signals:
void valueChanged(bool newvalue);
private:
class SwitchCircle;
class SwitchBackground;
void _update();
private:
bool _value;
int _duration;
QLinearGradient _lg;
QLinearGradient _lg2;
QLinearGradient _lg_disabled;
QColor _pencolor;
QColor _offcolor;
QColor _oncolor;
int _tol;
int _borderradius;
// This order for definition is important (these widgets overlap)
QLabel* _labeloff;
SwitchBackground* _background;
QLabel* _labelon;
SwitchCircle* _circle;
bool _enabled;
QPropertyAnimation* __btn_move;
QPropertyAnimation* __back_move;
};
class SwitchButton::SwitchBackground : public QWidget
{
Q_OBJECT
Q_DISABLE_COPY(SwitchBackground)
public:
explicit SwitchBackground(QWidget* parent = nullptr, QColor color = QColor(154, 205, 50), bool rect = false);
~SwitchBackground() override;
//-- QWidget methods
void paintEvent(QPaintEvent* event) override;
void setEnabled(bool);
private:
bool _rect;
int _borderradius;
QColor _color;
QColor _pencolor;
QLinearGradient _lg;
QLinearGradient _lg_disabled;
bool _enabled;
};
class SwitchButton::SwitchCircle : public QWidget
{
Q_OBJECT
Q_DISABLE_COPY(SwitchCircle)
public:
explicit SwitchCircle(QWidget* parent = nullptr, QColor color = QColor(255, 255, 255), bool rect = false);
~SwitchCircle() override;
//-- QWidget methods
void paintEvent(QPaintEvent* event) override;
void setEnabled(bool);
private:
bool _rect;
int _borderradius;
QColor _color;
QColor _pencolor;
QRadialGradient _rg;
QLinearGradient _lg;
QLinearGradient _lg_disabled;
bool _enabled;
};
(...).cpp
SwitchButton::SwitchButton(QWidget* parent, Style style)
: QWidget(parent)
, _value(false)
, _duration(100)
, _enabled(true)
{
_pencolor = QColor(120, 120, 120);
_lg = QLinearGradient(35, 30, 35, 0);
_lg.setColorAt(0, QColor(210, 210, 210));
_lg.setColorAt(0.25, QColor(255, 255, 255));
_lg.setColorAt(0.82, QColor(255, 255, 255));
_lg.setColorAt(1, QColor(210, 210, 210));
_lg2 = QLinearGradient(50, 30, 35, 0);
_lg2.setColorAt(0, QColor(230, 230, 230));
_lg2.setColorAt(0.25, QColor(255, 255, 255));
_lg2.setColorAt(0.82, QColor(255, 255, 255));
_lg2.setColorAt(1, QColor(230, 230, 230));
_lg_disabled = QLinearGradient(50, 30, 35, 0);
_lg_disabled.setColorAt(0, QColor(200, 200, 200));
_lg_disabled.setColorAt(0.25, QColor(230, 230, 230));
_lg_disabled.setColorAt(0.82, QColor(230, 230, 230));
_lg_disabled.setColorAt(1, QColor(200, 200, 200));
_offcolor = QColor(255, 255, 255);
_oncolor = QColor(154, 205, 50);
_tol = 0;
_borderradius = 12;
_labeloff = NEW QLabel(this);
_background = NEW SwitchBackground(this, _oncolor);
_labelon = NEW QLabel(this);
_circle = NEW SwitchCircle(this, _offcolor);
__btn_move = NEW QPropertyAnimation(this);
__back_move = NEW QPropertyAnimation(this);
__btn_move->setTargetObject(_circle);
__btn_move->setPropertyName("pos");
__back_move->setTargetObject(_background);
__back_move->setPropertyName("size");
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
_labeloff->setText("Off");
_labelon->setText("On");
_labeloff->move(31, 5);
_labelon->move(15, 5);
setFixedSize(QSize(60, 24));
if (style == Style::YESNO)
{
_labeloff->setText("No");
_labelon->setText("Yes");
_labeloff->move(33, 5);
_labelon->move(12, 5);
setFixedSize(QSize(60, 24));
}
else if (style == Style::BOOL)
{
_labeloff->setText("False");
_labelon->setText("True");
_labeloff->move(37, 5);
_labelon->move(12, 5);
setFixedSize(QSize(75, 24));
}
if (style == Style::EMPTY)
{
_labeloff->setText("");
_labelon->setText("");
_labeloff->move(31, 5);
_labelon->move(12, 5);
setFixedSize(QSize(45, 24));
}
_labeloff->setStyleSheet("color: rgb(120, 120, 120); font-weight: bold;");
_labelon->setStyleSheet("color: rgb(255, 255, 255); font-weight: bold;");
_background->resize(20, 20);
_background->move(2, 2);
_circle->move(2, 2);
}
SwitchButton::~SwitchButton()
{
delete _circle;
delete _background;
delete _labeloff;
delete _labelon;
delete __btn_move;
delete __back_move;
}
void SwitchButton::paintEvent(QPaintEvent*)
{
QPainter* painter = new QPainter;
painter->begin(this);
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(Qt::NoPen);
painter->setPen(pen);
painter->setBrush(_pencolor);
painter->drawRoundedRect(0, 0
, width(), height()
, 12, 12);
painter->setBrush(_lg);
painter->drawRoundedRect(1, 1
, width() - 2, height() - 2
, 10, 10);
painter->setBrush(QColor(210, 210, 210));
painter->drawRoundedRect(2, 2
, width() - 4, height() - 4
, 10, 10);
if (_enabled)
{
painter->setBrush(_lg2);
painter->drawRoundedRect(3, 3
, width() - 6, height() - 6
, 7, 7);
}
else
{
painter->setBrush(_lg_disabled);
painter->drawRoundedRect(3, 3
, width() - 6, height() - 6
, 7, 7);
}
painter->end();
}
void SwitchButton::mousePressEvent(QMouseEvent*)
{
if (!_enabled)
return;
__btn_move->stop();
__back_move->stop();
__btn_move->setDuration(_duration);
__back_move->setDuration(_duration);
int hback = 20;
QSize initial_size(hback, hback);
QSize final_size(width() - 4, hback);
int xi = 2;
int y = 2;
int xf = width() - 22;
if (_value)
{
final_size = QSize(hback, hback);
initial_size = QSize(width() - 4, hback);
xi = xf;
xf = 2;
}
__btn_move->setStartValue(QPoint(xi, y));
__btn_move->setEndValue(QPoint(xf, y));
__back_move->setStartValue(initial_size);
__back_move->setEndValue(final_size);
__btn_move->start();
__back_move->start();
// Assigning new current value
_value = !_value;
emit valueChanged(_value);
}
void SwitchButton::setEnabled(bool flag)
{
_enabled = flag;
_circle->setEnabled(flag);
_background->setEnabled(flag);
if (flag)
_labelon->show();
else
{
if (value())
_labelon->show();
else
_labelon->hide();
}
QWidget::setEnabled(flag);
}
void SwitchButton::setDuration(int time)
{
_duration = time;
}
void SwitchButton::setValue(bool flag)
{
if (flag == value())
return;
else
{
_value = flag;
_update();
setEnabled(_enabled);
}
}
bool SwitchButton::value() const
{
return _value;
}
void SwitchButton::_update()
{
int hback = 20;
QSize final_size(width() - 4, hback);
int y = 2;
int xf = width() - 22;
if (_value)
{
final_size = QSize(hback, hback);
xf = 2;
}
_circle->move(QPoint(xf, y));
_background->resize(final_size);
}
SwitchButton::SwitchBackground::SwitchBackground(QWidget* parent, QColor color, bool rect)
: QWidget(parent)
, _rect(rect)
, _borderradius(12)
, _color(color)
, _pencolor(QColor(170, 170, 170))
{
setFixedHeight(20);
_lg = QLinearGradient(0, 25, 70, 0);
_lg.setColorAt(0, QColor(154, 194, 50));
_lg.setColorAt(0.25, QColor(154, 210, 50));
_lg.setColorAt(0.95, QColor(154, 194, 50));
_lg_disabled = QLinearGradient(0, 25, 70, 0);
_lg_disabled.setColorAt(0, QColor(190, 190, 190));
_lg_disabled.setColorAt(0.25, QColor(230, 230, 230));
_lg_disabled.setColorAt(0.95, QColor(190, 190, 190));
if (_rect)
_borderradius = 0;
_enabled = true;
}
SwitchButton::SwitchBackground::~SwitchBackground()
{
}
void SwitchButton::SwitchBackground::paintEvent(QPaintEvent*)
{
QPainter* painter = new QPainter;
painter->begin(this);
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(Qt::NoPen);
painter->setPen(pen);
if (_enabled)
{
painter->setBrush(QColor(154, 190, 50));
painter->drawRoundedRect(0, 0
, width(), height()
, 10, 10);
painter->setBrush(_lg);
painter->drawRoundedRect(1, 1, width()-2, height()-2, 8, 8);
}
else
{
painter->setBrush(QColor(150, 150, 150));
painter->drawRoundedRect(0, 0
, width(), height()
, 10, 10);
painter->setBrush(_lg_disabled);
painter->drawRoundedRect(1, 1, width() - 2, height() - 2, 8, 8);
}
painter->end();
}
void SwitchButton::SwitchBackground::setEnabled(bool flag)
{
_enabled = flag;
}
SwitchButton::SwitchCircle::SwitchCircle(QWidget* parent, QColor color, bool rect)
: QWidget(parent)
, _rect(rect)
, _borderradius(12)
, _color(color)
, _pencolor(QColor(120, 120, 120))
{
setFixedSize(20, 20);
_rg = QRadialGradient(static_cast<int>(width() / 2), static_cast<int>(height() / 2), 12);
_rg.setColorAt(0, QColor(255, 255, 255));
_rg.setColorAt(0.6, QColor(255, 255, 255));
_rg.setColorAt(1, QColor(205, 205, 205));
_lg = QLinearGradient(3, 18, 20, 4);
_lg.setColorAt(0, QColor(255, 255, 255));
_lg.setColorAt(0.55, QColor(230, 230, 230));
_lg.setColorAt(0.72, QColor(255, 255, 255));
_lg.setColorAt(1, QColor(255, 255, 255));
_lg_disabled = QLinearGradient(3, 18, 20, 4);
_lg_disabled.setColorAt(0, QColor(230, 230, 230));
_lg_disabled.setColorAt(0.55, QColor(210, 210, 210));
_lg_disabled.setColorAt(0.72, QColor(230, 230, 230));
_lg_disabled.setColorAt(1, QColor(230, 230, 230));
_enabled = true;
}
SwitchButton::SwitchCircle::~SwitchCircle()
{
}
void SwitchButton::SwitchCircle::paintEvent(QPaintEvent*)
{
QPainter* painter = new QPainter;
painter->begin(this);
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(Qt::NoPen);
painter->setPen(pen);
painter->setBrush(_pencolor);
painter->drawEllipse(0, 0, 20, 20);
painter->setBrush(_rg);
painter->drawEllipse(1, 1, 18, 18);
painter->setBrush(QColor(210, 210, 210));
painter->drawEllipse(2, 2, 16, 16);
if (_enabled)
{
painter->setBrush(_lg);
painter->drawEllipse(3, 3, 14, 14);
}
else
{
painter->setBrush(_lg_disabled);
painter->drawEllipse(3, 3, 14, 14);
}
painter->end();
}
void SwitchButton::SwitchCircle::setEnabled(bool flag)
{
_enabled = flag;
}
Python implementation (Prototype; PyQt5)
Usage:
switchbtn = SwitchButton(self, "On", 15, "Off", 31, 60)
(...).py
from PyQt5 import QtWidgets, QtCore, QtGui
class SwitchButton(QtWidgets.QWidget):
def __init__(self, parent=None, w1="Yes", l1=12, w2="No", l2=33, width=60):
super(SwitchButton, self).__init__(parent)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.__labeloff = QtWidgets.QLabel(self)
self.__labeloff.setText(w2)
self.__labeloff.setStyleSheet("""color: rgb(120, 120, 120); font-weight: bold;""")
self.__background = Background(self)
self.__labelon = QtWidgets.QLabel(self)
self.__labelon.setText(w1)
self.__labelon.setStyleSheet("""color: rgb(255, 255, 255); font-weight: bold;""")
self.__circle = Circle(self)
self.__circlemove = None
self.__ellipsemove = None
self.__enabled = True
self.__duration = 100
self.__value = False
self.setFixedSize(width, 24)
self.__background.resize(20, 20)
self.__background.move(2, 2)
self.__circle.move(2, 2)
self.__labelon.move(l1, 5)
self.__labeloff.move(l2, 5)
def setDuration(self, time):
self.__duration = time
def mousePressEvent(self, event):
if not self.__enabled:
return
self.__circlemove = QtCore.QPropertyAnimation(self.__circle, b"pos")
self.__circlemove.setDuration(self.__duration)
self.__ellipsemove = QtCore.QPropertyAnimation(self.__background, b"size")
self.__ellipsemove.setDuration(self.__duration)
xs = 2
y = 2
xf = self.width()-22
hback = 20
isize = QtCore.QSize(hback, hback)
bsize = QtCore.QSize(self.width()-4, hback)
if self.__value:
xf = 2
xs = self.width()-22
bsize = QtCore.QSize(hback, hback)
isize = QtCore.QSize(self.width()-4, hback)
self.__circlemove.setStartValue(QtCore.QPoint(xs, y))
self.__circlemove.setEndValue(QtCore.QPoint(xf, y))
self.__ellipsemove.setStartValue(isize)
self.__ellipsemove.setEndValue(bsize)
self.__circlemove.start()
self.__ellipsemove.start()
self.__value = not self.__value
def paintEvent(self, event):
s = self.size()
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
pen = QtGui.QPen(QtCore.Qt.NoPen)
qp.setPen(pen)
qp.setBrush(QtGui.QColor(120, 120, 120))
qp.drawRoundedRect(0, 0, s.width(), s.height(), 12, 12)
lg = QtGui.QLinearGradient(35, 30, 35, 0)
lg.setColorAt(0, QtGui.QColor(210, 210, 210, 255))
lg.setColorAt(0.25, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(0.82, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(1, QtGui.QColor(210, 210, 210, 255))
qp.setBrush(lg)
qp.drawRoundedRect(1, 1, s.width()-2, s.height()-2, 10, 10)
qp.setBrush(QtGui.QColor(210, 210, 210))
qp.drawRoundedRect(2, 2, s.width() - 4, s.height() - 4, 10, 10)
if self.__enabled:
lg = QtGui.QLinearGradient(50, 30, 35, 0)
lg.setColorAt(0, QtGui.QColor(230, 230, 230, 255))
lg.setColorAt(0.25, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(0.82, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(1, QtGui.QColor(230, 230, 230, 255))
qp.setBrush(lg)
qp.drawRoundedRect(3, 3, s.width() - 6, s.height() - 6, 7, 7)
else:
lg = QtGui.QLinearGradient(50, 30, 35, 0)
lg.setColorAt(0, QtGui.QColor(200, 200, 200, 255))
lg.setColorAt(0.25, QtGui.QColor(230, 230, 230, 255))
lg.setColorAt(0.82, QtGui.QColor(230, 230, 230, 255))
lg.setColorAt(1, QtGui.QColor(200, 200, 200, 255))
qp.setBrush(lg)
qp.drawRoundedRect(3, 3, s.width() - 6, s.height() - 6, 7, 7)
qp.end()
class Circle(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Circle, self).__init__(parent)
self.__enabled = True
self.setFixedSize(20, 20)
def paintEvent(self, event):
s = self.size()
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
qp.setPen(QtCore.Qt.NoPen)
qp.setBrush(QtGui.QColor(120, 120, 120))
qp.drawEllipse(0, 0, 20, 20)
rg = QtGui.QRadialGradient(int(self.width() / 2), int(self.height() / 2), 12)
rg.setColorAt(0, QtGui.QColor(255, 255, 255))
rg.setColorAt(0.6, QtGui.QColor(255, 255, 255))
rg.setColorAt(1, QtGui.QColor(205, 205, 205))
qp.setBrush(QtGui.QBrush(rg))
qp.drawEllipse(1,1, 18, 18)
qp.setBrush(QtGui.QColor(210, 210, 210))
qp.drawEllipse(2, 2, 16, 16)
if self.__enabled:
lg = QtGui.QLinearGradient(3, 18,20, 4)
lg.setColorAt(0, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(0.55, QtGui.QColor(230, 230, 230, 255))
lg.setColorAt(0.72, QtGui.QColor(255, 255, 255, 255))
lg.setColorAt(1, QtGui.QColor(255, 255, 255, 255))
qp.setBrush(lg)
qp.drawEllipse(3,3, 14, 14)
else:
lg = QtGui.QLinearGradient(3, 18, 20, 4)
lg.setColorAt(0, QtGui.QColor(230, 230, 230))
lg.setColorAt(0.55, QtGui.QColor(210, 210, 210))
lg.setColorAt(0.72, QtGui.QColor(230, 230, 230))
lg.setColorAt(1, QtGui.QColor(230, 230, 230))
qp.setBrush(lg)
qp.drawEllipse(3, 3, 14, 14)
qp.end()
class Background(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Background, self).__init__(parent)
self.__enabled = True
self.setFixedHeight(20)
def paintEvent(self, event):
s = self.size()
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
pen = QtGui.QPen(QtCore.Qt.NoPen)
qp.setPen(pen)
qp.setBrush(QtGui.QColor(154,205,50))
if self.__enabled:
qp.setBrush(QtGui.QColor(154, 190, 50))
qp.drawRoundedRect(0, 0, s.width(), s.height(), 10, 10)
lg = QtGui.QLinearGradient(0, 25, 70, 0)
lg.setColorAt(0, QtGui.QColor(154, 184, 50))
lg.setColorAt(0.35, QtGui.QColor(154, 210, 50))
lg.setColorAt(0.85, QtGui.QColor(154, 184, 50))
qp.setBrush(lg)
qp.drawRoundedRect(1, 1, s.width() - 2, s.height() - 2, 8, 8)
else:
qp.setBrush(QtGui.QColor(150, 150, 150))
qp.drawRoundedRect(0, 0, s.width(), s.height(), 10, 10)
lg = QtGui.QLinearGradient(5, 25, 60, 0)
lg.setColorAt(0, QtGui.QColor(190, 190, 190))
lg.setColorAt(0.35, QtGui.QColor(230, 230, 230))
lg.setColorAt(0.85, QtGui.QColor(190, 190, 190))
qp.setBrush(lg)
qp.drawRoundedRect(1, 1, s.width() - 2, s.height() - 2, 8, 8)
qp.end()
Here is a Python 3 / PyQt5 implementation of @IMAN4K’s answer.
Improvements over the original implementation:
- Thumb-size can be larger/smaller than the track size
- Use current app’s palette for coloring
- Emit toggled/clicked signals when clicked
- Various other fixes
from PyQt5.QtCore import QPropertyAnimation, QRectF, QSize, Qt, pyqtProperty
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import (
QAbstractButton,
QApplication,
QHBoxLayout,
QSizePolicy,
QWidget,
)
class Switch(QAbstractButton):
def __init__(self, parent=None, track_radius=10, thumb_radius=8):
super().__init__(parent=parent)
self.setCheckable(True)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self._track_radius = track_radius
self._thumb_radius = thumb_radius
self._margin = max(0, self._thumb_radius - self._track_radius)
self._base_offset = max(self._thumb_radius, self._track_radius)
self._end_offset = {
True: lambda: self.width() - self._base_offset,
False: lambda: self._base_offset,
}
self._offset = self._base_offset
palette = self.palette()
if self._thumb_radius > self._track_radius:
self._track_color = {
True: palette.highlight(),
False: palette.dark(),
}
self._thumb_color = {
True: palette.highlight(),
False: palette.light(),
}
self._text_color = {
True: palette.highlightedText().color(),
False: palette.dark().color(),
}
self._thumb_text = {
True: '',
False: '',
}
self._track_opacity = 0.5
else:
self._thumb_color = {
True: palette.highlightedText(),
False: palette.light(),
}
self._track_color = {
True: palette.highlight(),
False: palette.dark(),
}
self._text_color = {
True: palette.highlight().color(),
False: palette.dark().color(),
}
self._thumb_text = {
True: '✔',
False: '✕',
}
self._track_opacity = 1
@pyqtProperty(int)
def offset(self):
return self._offset
@offset.setter
def offset(self, value):
self._offset = value
self.update()
def sizeHint(self): # pylint: disable=invalid-name
return QSize(
4 * self._track_radius + 2 * self._margin,
2 * self._track_radius + 2 * self._margin,
)
def setChecked(self, checked):
super().setChecked(checked)
self.offset = self._end_offset[checked]()
def resizeEvent(self, event):
super().resizeEvent(event)
self.offset = self._end_offset[self.isChecked()]()
def paintEvent(self, event): # pylint: disable=invalid-name, unused-argument
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing, True)
p.setPen(Qt.NoPen)
track_opacity = self._track_opacity
thumb_opacity = 1.0
text_opacity = 1.0
if self.isEnabled():
track_brush = self._track_color[self.isChecked()]
thumb_brush = self._thumb_color[self.isChecked()]
text_color = self._text_color[self.isChecked()]
else:
track_opacity *= 0.8
track_brush = self.palette().shadow()
thumb_brush = self.palette().mid()
text_color = self.palette().shadow().color()
p.setBrush(track_brush)
p.setOpacity(track_opacity)
p.drawRoundedRect(
self._margin,
self._margin,
self.width() - 2 * self._margin,
self.height() - 2 * self._margin,
self._track_radius,
self._track_radius,
)
p.setBrush(thumb_brush)
p.setOpacity(thumb_opacity)
p.drawEllipse(
self.offset - self._thumb_radius,
self._base_offset - self._thumb_radius,
2 * self._thumb_radius,
2 * self._thumb_radius,
)
p.setPen(text_color)
p.setOpacity(text_opacity)
font = p.font()
font.setPixelSize(1.5 * self._thumb_radius)
p.setFont(font)
p.drawText(
QRectF(
self.offset - self._thumb_radius,
self._base_offset - self._thumb_radius,
2 * self._thumb_radius,
2 * self._thumb_radius,
),
Qt.AlignCenter,
self._thumb_text[self.isChecked()],
)
def mouseReleaseEvent(self, event): # pylint: disable=invalid-name
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
anim = QPropertyAnimation(self, b'offset', self)
anim.setDuration(120)
anim.setStartValue(self.offset)
anim.setEndValue(self._end_offset[self.isChecked()]())
anim.start()
def enterEvent(self, event): # pylint: disable=invalid-name
self.setCursor(Qt.PointingHandCursor)
super().enterEvent(event)
def main():
app = QApplication([])
# Thumb size < track size (Gitlab style)
s1 = Switch()
s1.toggled.connect(lambda c: print('toggled', c))
s1.clicked.connect(lambda c: print('clicked', c))
s1.pressed.connect(lambda: print('pressed'))
s1.released.connect(lambda: print('released'))
s2 = Switch()
s2.setEnabled(False)
# Thumb size > track size (Android style)
s3 = Switch(thumb_radius=11, track_radius=8)
s4 = Switch(thumb_radius=11, track_radius=8)
s4.setEnabled(False)
l = QHBoxLayout()
l.addWidget(s1)
l.addWidget(s2)
l.addWidget(s3)
l.addWidget(s4)
w = QWidget()
w.setLayout(l)
w.show()
app.exec()
if __name__ == '__main__':
main()
@piccy's suggestion is what I've done for such a toggle switch previously. With a few tweaks tho.
We had to emulate the behaviours similar to the iOS on/off switches. Meaning you needed a gradual movement which you won't have with slider being with a limit of 0-1 without external animations.
Hence what I did was set the value range for the slider to be the same as the max width of the slider.
Then connect the slider released signal and check if the value is less than half the maximum and if so set slider value to 0 else slider value to max.
This will give you a good drag effect and clip to extremes when you release the mouse.
If you want the slider to just toggle when clicked on the other side without any drag connect the slider value changed signal and check the new value to be closer to either extreme and set it as the slider value if the slider is not in its down state. Do not change slider value if slider is down since you might then break the previous drag motion.