I am trying to use an element which is the equivalent of Android Switches in Qt. I have found a ToggleSwitch in QML, but nothing in the actual C++ Qt libs. Am I just missing something or will I have to reimplement this widget myself?
Toggle Switches have an operating lever that can be pushed up and down or left and right to switch an electrical circuit. A “toggle” is a small wooden rod that is used as a clothing fastener in the place of buttons.
The Qt platform itself contains a powerful JavaScript engine, enabling the behaviors behind the declared Qt Quick UI controls to be written in pure JavaScript.
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(); }
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With