From 0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Tue, 17 Jul 2018 16:37:25 +0300 Subject: Move all files under src/ --- src/ui/Avatar.cc | 147 --------- src/ui/Avatar.cpp | 147 +++++++++ src/ui/Avatar.h | 47 +++ src/ui/Badge.cc | 218 -------------- src/ui/Badge.cpp | 218 ++++++++++++++ src/ui/Badge.h | 62 ++++ src/ui/DropShadow.h | 111 +++++++ src/ui/FlatButton.cc | 719 -------------------------------------------- src/ui/FlatButton.cpp | 719 ++++++++++++++++++++++++++++++++++++++++++++ src/ui/FlatButton.h | 185 ++++++++++++ src/ui/FloatingButton.cc | 95 ------ src/ui/FloatingButton.cpp | 95 ++++++ src/ui/FloatingButton.h | 26 ++ src/ui/InfoMessage.cpp | 2 +- src/ui/InfoMessage.h | 47 +++ src/ui/Label.cc | 44 --- src/ui/Label.cpp | 44 +++ src/ui/Label.h | 25 ++ src/ui/LoadingIndicator.cc | 85 ------ src/ui/LoadingIndicator.cpp | 85 ++++++ src/ui/LoadingIndicator.h | 38 +++ src/ui/Menu.h | 32 ++ src/ui/OverlayModal.cc | 60 ---- src/ui/OverlayModal.cpp | 60 ++++ src/ui/OverlayModal.h | 45 +++ src/ui/OverlayWidget.cc | 72 ----- src/ui/OverlayWidget.cpp | 72 +++++ src/ui/OverlayWidget.h | 21 ++ src/ui/Painter.h | 161 ++++++++++ src/ui/RaisedButton.cc | 89 ------ src/ui/RaisedButton.cpp | 89 ++++++ src/ui/RaisedButton.h | 28 ++ src/ui/Ripple.cc | 107 ------- src/ui/Ripple.cpp | 107 +++++++ src/ui/Ripple.h | 145 +++++++++ src/ui/RippleOverlay.cc | 62 ---- src/ui/RippleOverlay.cpp | 62 ++++ src/ui/RippleOverlay.h | 57 ++++ src/ui/ScrollBar.cc | 59 ---- src/ui/ScrollBar.cpp | 59 ++++ src/ui/ScrollBar.h | 54 ++++ src/ui/SnackBar.cc | 141 --------- src/ui/SnackBar.cpp | 141 +++++++++ src/ui/SnackBar.h | 79 +++++ src/ui/TextField.cc | 363 ---------------------- src/ui/TextField.cpp | 363 ++++++++++++++++++++++ src/ui/TextField.h | 174 +++++++++++ src/ui/Theme.cc | 73 ----- src/ui/Theme.cpp | 73 +++++ src/ui/Theme.h | 97 ++++++ src/ui/ThemeManager.cc | 19 -- src/ui/ThemeManager.cpp | 19 ++ src/ui/ThemeManager.h | 31 ++ src/ui/ToggleButton.cc | 211 ------------- src/ui/ToggleButton.cpp | 211 +++++++++++++ src/ui/ToggleButton.h | 110 +++++++ 56 files changed, 4140 insertions(+), 2565 deletions(-) delete mode 100644 src/ui/Avatar.cc create mode 100644 src/ui/Avatar.cpp create mode 100644 src/ui/Avatar.h delete mode 100644 src/ui/Badge.cc create mode 100644 src/ui/Badge.cpp create mode 100644 src/ui/Badge.h create mode 100644 src/ui/DropShadow.h delete mode 100644 src/ui/FlatButton.cc create mode 100644 src/ui/FlatButton.cpp create mode 100644 src/ui/FlatButton.h delete mode 100644 src/ui/FloatingButton.cc create mode 100644 src/ui/FloatingButton.cpp create mode 100644 src/ui/FloatingButton.h create mode 100644 src/ui/InfoMessage.h delete mode 100644 src/ui/Label.cc create mode 100644 src/ui/Label.cpp create mode 100644 src/ui/Label.h delete mode 100644 src/ui/LoadingIndicator.cc create mode 100644 src/ui/LoadingIndicator.cpp create mode 100644 src/ui/LoadingIndicator.h create mode 100644 src/ui/Menu.h delete mode 100644 src/ui/OverlayModal.cc create mode 100644 src/ui/OverlayModal.cpp create mode 100644 src/ui/OverlayModal.h delete mode 100644 src/ui/OverlayWidget.cc create mode 100644 src/ui/OverlayWidget.cpp create mode 100644 src/ui/OverlayWidget.h create mode 100644 src/ui/Painter.h delete mode 100644 src/ui/RaisedButton.cc create mode 100644 src/ui/RaisedButton.cpp create mode 100644 src/ui/RaisedButton.h delete mode 100644 src/ui/Ripple.cc create mode 100644 src/ui/Ripple.cpp create mode 100644 src/ui/Ripple.h delete mode 100644 src/ui/RippleOverlay.cc create mode 100644 src/ui/RippleOverlay.cpp create mode 100644 src/ui/RippleOverlay.h delete mode 100644 src/ui/ScrollBar.cc create mode 100644 src/ui/ScrollBar.cpp create mode 100644 src/ui/ScrollBar.h delete mode 100644 src/ui/SnackBar.cc create mode 100644 src/ui/SnackBar.cpp create mode 100644 src/ui/SnackBar.h delete mode 100644 src/ui/TextField.cc create mode 100644 src/ui/TextField.cpp create mode 100644 src/ui/TextField.h delete mode 100644 src/ui/Theme.cc create mode 100644 src/ui/Theme.cpp create mode 100644 src/ui/Theme.h delete mode 100644 src/ui/ThemeManager.cc create mode 100644 src/ui/ThemeManager.cpp create mode 100644 src/ui/ThemeManager.h delete mode 100644 src/ui/ToggleButton.cc create mode 100644 src/ui/ToggleButton.cpp create mode 100644 src/ui/ToggleButton.h (limited to 'src/ui') diff --git a/src/ui/Avatar.cc b/src/ui/Avatar.cc deleted file mode 100644 index 2f10db39..00000000 --- a/src/ui/Avatar.cc +++ /dev/null @@ -1,147 +0,0 @@ -#include - -#include "Avatar.h" -#include "Utils.h" - -Avatar::Avatar(QWidget *parent) - : QWidget(parent) -{ - size_ = ui::AvatarSize; - type_ = ui::AvatarType::Letter; - letter_ = "A"; - - QFont _font(font()); - _font.setPointSizeF(ui::FontSize); - setFont(_font); - - QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setSizePolicy(policy); -} - -QColor -Avatar::textColor() const -{ - if (!text_color_.isValid()) - return QColor("black"); - - return text_color_; -} - -QColor -Avatar::backgroundColor() const -{ - if (!text_color_.isValid()) - return QColor("white"); - - return background_color_; -} - -int -Avatar::size() const -{ - return size_; -} - -QSize -Avatar::sizeHint() const -{ - return QSize(size_ + 2, size_ + 2); -} - -void -Avatar::setTextColor(const QColor &color) -{ - text_color_ = color; -} - -void -Avatar::setBackgroundColor(const QColor &color) -{ - background_color_ = color; -} - -void -Avatar::setSize(int size) -{ - size_ = size; - - if (!image_.isNull()) - pixmap_ = utils::scaleImageToPixmap(image_, size_); - - QFont _font(font()); - _font.setPointSizeF(size_ * (ui::FontSize) / 40); - - setFont(_font); - update(); -} - -void -Avatar::setLetter(const QString &letter) -{ - letter_ = letter; - type_ = ui::AvatarType::Letter; - update(); -} - -void -Avatar::setImage(const QImage &image) -{ - image_ = image; - type_ = ui::AvatarType::Image; - pixmap_ = utils::scaleImageToPixmap(image_, size_); - update(); -} - -void -Avatar::setIcon(const QIcon &icon) -{ - icon_ = icon; - type_ = ui::AvatarType::Icon; - update(); -} - -void -Avatar::paintEvent(QPaintEvent *) -{ - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - QRect r = rect(); - const int hs = size_ / 2; - - if (type_ != ui::AvatarType::Image) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(backgroundColor()); - - painter.setPen(Qt::NoPen); - painter.setBrush(brush); - painter.drawEllipse(r.center(), hs, hs); - } - - switch (type_) { - case ui::AvatarType::Icon: { - icon_.paint(&painter, - QRect((width() - hs) / 2, (height() - hs) / 2, hs, hs), - Qt::AlignCenter, - QIcon::Normal); - break; - } - case ui::AvatarType::Image: { - QPainterPath ppath; - ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_); - painter.setClipPath(ppath); - painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), - pixmap_); - break; - } - case ui::AvatarType::Letter: { - painter.setPen(textColor()); - painter.setBrush(Qt::NoBrush); - painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_); - break; - } - default: - break; - } -} diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp new file mode 100644 index 00000000..4b4cd272 --- /dev/null +++ b/src/ui/Avatar.cpp @@ -0,0 +1,147 @@ +#include + +#include "Utils.h" +#include "ui/Avatar.h" + +Avatar::Avatar(QWidget *parent) + : QWidget(parent) +{ + size_ = ui::AvatarSize; + type_ = ui::AvatarType::Letter; + letter_ = "A"; + + QFont _font(font()); + _font.setPointSizeF(ui::FontSize); + setFont(_font); + + QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setSizePolicy(policy); +} + +QColor +Avatar::textColor() const +{ + if (!text_color_.isValid()) + return QColor("black"); + + return text_color_; +} + +QColor +Avatar::backgroundColor() const +{ + if (!text_color_.isValid()) + return QColor("white"); + + return background_color_; +} + +int +Avatar::size() const +{ + return size_; +} + +QSize +Avatar::sizeHint() const +{ + return QSize(size_ + 2, size_ + 2); +} + +void +Avatar::setTextColor(const QColor &color) +{ + text_color_ = color; +} + +void +Avatar::setBackgroundColor(const QColor &color) +{ + background_color_ = color; +} + +void +Avatar::setSize(int size) +{ + size_ = size; + + if (!image_.isNull()) + pixmap_ = utils::scaleImageToPixmap(image_, size_); + + QFont _font(font()); + _font.setPointSizeF(size_ * (ui::FontSize) / 40); + + setFont(_font); + update(); +} + +void +Avatar::setLetter(const QString &letter) +{ + letter_ = letter; + type_ = ui::AvatarType::Letter; + update(); +} + +void +Avatar::setImage(const QImage &image) +{ + image_ = image; + type_ = ui::AvatarType::Image; + pixmap_ = utils::scaleImageToPixmap(image_, size_); + update(); +} + +void +Avatar::setIcon(const QIcon &icon) +{ + icon_ = icon; + type_ = ui::AvatarType::Icon; + update(); +} + +void +Avatar::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRect r = rect(); + const int hs = size_ / 2; + + if (type_ != ui::AvatarType::Image) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor()); + + painter.setPen(Qt::NoPen); + painter.setBrush(brush); + painter.drawEllipse(r.center(), hs, hs); + } + + switch (type_) { + case ui::AvatarType::Icon: { + icon_.paint(&painter, + QRect((width() - hs) / 2, (height() - hs) / 2, hs, hs), + Qt::AlignCenter, + QIcon::Normal); + break; + } + case ui::AvatarType::Image: { + QPainterPath ppath; + ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_); + painter.setClipPath(ppath); + painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), + pixmap_); + break; + } + case ui::AvatarType::Letter: { + painter.setPen(textColor()); + painter.setBrush(Qt::NoBrush); + painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_); + break; + } + default: + break; + } +} diff --git a/src/ui/Avatar.h b/src/ui/Avatar.h new file mode 100644 index 00000000..41967af5 --- /dev/null +++ b/src/ui/Avatar.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +#include "Theme.h" + +class Avatar : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) + +public: + explicit Avatar(QWidget *parent = 0); + + void setBackgroundColor(const QColor &color); + void setIcon(const QIcon &icon); + void setImage(const QImage &image); + void setLetter(const QString &letter); + void setSize(int size); + void setTextColor(const QColor &color); + + QColor backgroundColor() const; + QColor textColor() const; + int size() const; + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + + ui::AvatarType type_; + QString letter_; + QColor background_color_; + QColor text_color_; + QIcon icon_; + QImage image_; + QPixmap pixmap_; + int size_; +}; diff --git a/src/ui/Badge.cc b/src/ui/Badge.cc deleted file mode 100644 index 6701f9b7..00000000 --- a/src/ui/Badge.cc +++ /dev/null @@ -1,218 +0,0 @@ -#include - -#include "Badge.h" - -Badge::Badge(QWidget *parent) - : OverlayWidget(parent) -{ - init(); -} - -Badge::Badge(const QIcon &icon, QWidget *parent) - : OverlayWidget(parent) -{ - init(); - setIcon(icon); -} - -Badge::Badge(const QString &text, QWidget *parent) - : OverlayWidget(parent) -{ - init(); - setText(text); -} - -void -Badge::init() -{ - x_ = 0; - y_ = 0; - // TODO: Make padding configurable. - padding_ = 5; - diameter_ = 24; - - setAttribute(Qt::WA_TransparentForMouseEvents); - - QFont _font(font()); - _font.setPointSizeF(7.5); - _font.setStyleName("Bold"); - - setFont(_font); - setText(""); -} - -QString -Badge::text() const -{ - return text_; -} - -QIcon -Badge::icon() const -{ - return icon_; -} - -QSize -Badge::sizeHint() const -{ - const int d = diameter(); - return QSize(d + 4, d + 4); -} - -qreal -Badge::relativeYPosition() const -{ - return y_; -} - -qreal -Badge::relativeXPosition() const -{ - return x_; -} - -QPointF -Badge::relativePosition() const -{ - return QPointF(x_, y_); -} - -QColor -Badge::backgroundColor() const -{ - if (!background_color_.isValid()) - return QColor("black"); - - return background_color_; -} - -QColor -Badge::textColor() const -{ - if (!text_color_.isValid()) - return QColor("white"); - - return text_color_; -} - -void -Badge::setTextColor(const QColor &color) -{ - text_color_ = color; -} - -void -Badge::setBackgroundColor(const QColor &color) -{ - background_color_ = color; -} - -void -Badge::setRelativePosition(const QPointF &pos) -{ - setRelativePosition(pos.x(), pos.y()); -} - -void -Badge::setRelativePosition(qreal x, qreal y) -{ - x_ = x; - y_ = y; - update(); -} - -void -Badge::setRelativeXPosition(qreal x) -{ - x_ = x; - update(); -} - -void -Badge::setRelativeYPosition(qreal y) -{ - y_ = y; - update(); -} - -void -Badge::setIcon(const QIcon &icon) -{ - icon_ = icon; - update(); -} - -void -Badge::setText(const QString &text) -{ - text_ = text; - - if (!icon_.isNull()) - icon_ = QIcon(); - - size_ = fontMetrics().size(Qt::TextShowMnemonic, text); - - update(); -} - -void -Badge::setDiameter(int diameter) -{ - if (diameter > 0) { - diameter_ = diameter; - update(); - } -} - -void -Badge::paintEvent(QPaintEvent *) -{ - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - painter.translate(x_, y_); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - - painter.setBrush(brush); - painter.setPen(Qt::NoPen); - - const int d = diameter(); - - QRectF r(0, 0, d, d); - r.translate(QPointF((width() - d), (height() - d)) / 2); - - if (icon_.isNull()) { - QPen pen; - // TODO: Make badge width configurable. - pen.setWidth(1); - pen.setColor(textColor()); - - painter.setPen(pen); - painter.drawEllipse(r); - - painter.setPen(textColor()); - painter.setBrush(Qt::NoBrush); - painter.drawText(r.translated(0, -0.5), Qt::AlignCenter, text_); - } else { - painter.drawEllipse(r); - QRectF q(0, 0, 16, 16); - q.moveCenter(r.center()); - QPixmap pixmap = icon().pixmap(16, 16); - QPainter icon(&pixmap); - icon.setCompositionMode(QPainter::CompositionMode_SourceIn); - icon.fillRect(pixmap.rect(), textColor()); - painter.drawPixmap(q.toRect(), pixmap); - } -} - -int -Badge::diameter() const -{ - if (icon_.isNull()) { - return qMax(size_.width(), size_.height()) + padding_; - } - - return diameter_; -} diff --git a/src/ui/Badge.cpp b/src/ui/Badge.cpp new file mode 100644 index 00000000..6701f9b7 --- /dev/null +++ b/src/ui/Badge.cpp @@ -0,0 +1,218 @@ +#include + +#include "Badge.h" + +Badge::Badge(QWidget *parent) + : OverlayWidget(parent) +{ + init(); +} + +Badge::Badge(const QIcon &icon, QWidget *parent) + : OverlayWidget(parent) +{ + init(); + setIcon(icon); +} + +Badge::Badge(const QString &text, QWidget *parent) + : OverlayWidget(parent) +{ + init(); + setText(text); +} + +void +Badge::init() +{ + x_ = 0; + y_ = 0; + // TODO: Make padding configurable. + padding_ = 5; + diameter_ = 24; + + setAttribute(Qt::WA_TransparentForMouseEvents); + + QFont _font(font()); + _font.setPointSizeF(7.5); + _font.setStyleName("Bold"); + + setFont(_font); + setText(""); +} + +QString +Badge::text() const +{ + return text_; +} + +QIcon +Badge::icon() const +{ + return icon_; +} + +QSize +Badge::sizeHint() const +{ + const int d = diameter(); + return QSize(d + 4, d + 4); +} + +qreal +Badge::relativeYPosition() const +{ + return y_; +} + +qreal +Badge::relativeXPosition() const +{ + return x_; +} + +QPointF +Badge::relativePosition() const +{ + return QPointF(x_, y_); +} + +QColor +Badge::backgroundColor() const +{ + if (!background_color_.isValid()) + return QColor("black"); + + return background_color_; +} + +QColor +Badge::textColor() const +{ + if (!text_color_.isValid()) + return QColor("white"); + + return text_color_; +} + +void +Badge::setTextColor(const QColor &color) +{ + text_color_ = color; +} + +void +Badge::setBackgroundColor(const QColor &color) +{ + background_color_ = color; +} + +void +Badge::setRelativePosition(const QPointF &pos) +{ + setRelativePosition(pos.x(), pos.y()); +} + +void +Badge::setRelativePosition(qreal x, qreal y) +{ + x_ = x; + y_ = y; + update(); +} + +void +Badge::setRelativeXPosition(qreal x) +{ + x_ = x; + update(); +} + +void +Badge::setRelativeYPosition(qreal y) +{ + y_ = y; + update(); +} + +void +Badge::setIcon(const QIcon &icon) +{ + icon_ = icon; + update(); +} + +void +Badge::setText(const QString &text) +{ + text_ = text; + + if (!icon_.isNull()) + icon_ = QIcon(); + + size_ = fontMetrics().size(Qt::TextShowMnemonic, text); + + update(); +} + +void +Badge::setDiameter(int diameter) +{ + if (diameter > 0) { + diameter_ = diameter; + update(); + } +} + +void +Badge::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.translate(x_, y_); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + const int d = diameter(); + + QRectF r(0, 0, d, d); + r.translate(QPointF((width() - d), (height() - d)) / 2); + + if (icon_.isNull()) { + QPen pen; + // TODO: Make badge width configurable. + pen.setWidth(1); + pen.setColor(textColor()); + + painter.setPen(pen); + painter.drawEllipse(r); + + painter.setPen(textColor()); + painter.setBrush(Qt::NoBrush); + painter.drawText(r.translated(0, -0.5), Qt::AlignCenter, text_); + } else { + painter.drawEllipse(r); + QRectF q(0, 0, 16, 16); + q.moveCenter(r.center()); + QPixmap pixmap = icon().pixmap(16, 16); + QPainter icon(&pixmap); + icon.setCompositionMode(QPainter::CompositionMode_SourceIn); + icon.fillRect(pixmap.rect(), textColor()); + painter.drawPixmap(q.toRect(), pixmap); + } +} + +int +Badge::diameter() const +{ + if (icon_.isNull()) { + return qMax(size_.width(), size_.height()) + padding_; + } + + return diameter_; +} diff --git a/src/ui/Badge.h b/src/ui/Badge.h new file mode 100644 index 00000000..fd73ad30 --- /dev/null +++ b/src/ui/Badge.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +#include "OverlayWidget.h" + +class Badge : public OverlayWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) + Q_PROPERTY(QPointF relativePosition WRITE setRelativePosition READ relativePosition) + +public: + explicit Badge(QWidget *parent = 0); + explicit Badge(const QIcon &icon, QWidget *parent = 0); + explicit Badge(const QString &text, QWidget *parent = 0); + + void setBackgroundColor(const QColor &color); + void setTextColor(const QColor &color); + void setIcon(const QIcon &icon); + void setRelativePosition(const QPointF &pos); + void setRelativePosition(qreal x, qreal y); + void setRelativeXPosition(qreal x); + void setRelativeYPosition(qreal y); + void setText(const QString &text); + void setDiameter(int diameter); + + QIcon icon() const; + QString text() const; + QColor backgroundColor() const; + QColor textColor() const; + QPointF relativePosition() const; + QSize sizeHint() const override; + qreal relativeXPosition() const; + qreal relativeYPosition() const; + + int diameter() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + + QColor background_color_; + QColor text_color_; + + QIcon icon_; + QSize size_; + QString text_; + + int padding_; + int diameter_; + + qreal x_; + qreal y_; +}; diff --git a/src/ui/DropShadow.h b/src/ui/DropShadow.h new file mode 100644 index 00000000..b7ba1985 --- /dev/null +++ b/src/ui/DropShadow.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +class DropShadow +{ +public: + static void draw(QPainter &painter, + qint16 margin, + qreal radius, + QColor start, + QColor end, + qreal startPosition, + qreal endPosition0, + qreal endPosition1, + qreal width, + qreal height) + { + painter.setPen(Qt::NoPen); + + QLinearGradient gradient; + gradient.setColorAt(startPosition, start); + gradient.setColorAt(endPosition0, end); + + // Right + QPointF right0(width - margin, height / 2); + QPointF right1(width, height / 2); + gradient.setStart(right0); + gradient.setFinalStop(right1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect( + QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), + 0.0, + 0.0); + + // Left + QPointF left0(margin, height / 2); + QPointF left1(0, height / 2); + gradient.setStart(left0); + gradient.setFinalStop(left1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect( + QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0); + + // Top + QPointF top0(width / 2, margin); + QPointF top1(width / 2, 0); + gradient.setStart(top0); + gradient.setFinalStop(top1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect( + QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0); + + // Bottom + QPointF bottom0(width / 2, height - margin); + QPointF bottom1(width / 2, height); + gradient.setStart(bottom0); + gradient.setFinalStop(bottom1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect( + QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), + 0.0, + 0.0); + + // BottomRight + QPointF bottomright0(width - margin, height - margin); + QPointF bottomright1(width, height); + gradient.setStart(bottomright0); + gradient.setFinalStop(bottomright1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(bottomright0, bottomright1), 0.0, 0.0); + + // BottomLeft + QPointF bottomleft0(margin, height - margin); + QPointF bottomleft1(0, height); + gradient.setStart(bottomleft0); + gradient.setFinalStop(bottomleft1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0); + + // TopLeft + QPointF topleft0(margin, margin); + QPointF topleft1(0, 0); + gradient.setStart(topleft0); + gradient.setFinalStop(topleft1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(topleft0, topleft1), 0.0, 0.0); + + // TopRight + QPointF topright0(width - margin, margin); + QPointF topright1(width, 0); + gradient.setStart(topright0); + gradient.setFinalStop(topright1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(topright0, topright1), 0.0, 0.0); + + // Widget + painter.setBrush(QBrush("#FFFFFF")); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawRoundRect( + QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), + radius, + radius); + } +}; diff --git a/src/ui/FlatButton.cc b/src/ui/FlatButton.cc deleted file mode 100644 index 45a7683e..00000000 --- a/src/ui/FlatButton.cc +++ /dev/null @@ -1,719 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "FlatButton.h" -#include "Ripple.h" -#include "RippleOverlay.h" -#include "ThemeManager.h" - -// The ampersand is automatically set in QPushButton or QCheckbx -// by KDEPlatformTheme plugin in Qt5. -// [https://bugs.kde.org/show_bug.cgi?id=337491] -// -// A workaroud is to add -// -// [Development] -// AutoCheckAccelerators=false -// -// to ~/.config/kdeglobals -static QString -removeKDEAccelerators(QString text) -{ - return text.remove(QChar('&')); -} - -void -FlatButton::init() -{ - ripple_overlay_ = new RippleOverlay(this); - state_machine_ = new FlatButtonStateMachine(this); - role_ = ui::Role::Default; - ripple_style_ = ui::RippleStyle::PositionedRipple; - icon_placement_ = ui::ButtonIconPlacement::LeftIcon; - overlay_style_ = ui::OverlayStyle::GrayOverlay; - bg_mode_ = Qt::TransparentMode; - fixed_ripple_radius_ = 64; - corner_radius_ = 3; - base_opacity_ = 0.13; - font_size_ = 10; // 10.5; - use_fixed_ripple_radius_ = false; - - setStyle(&ThemeManager::instance()); - setAttribute(Qt::WA_Hover); - setMouseTracking(true); - setCursor(QCursor(Qt::PointingHandCursor)); - - QPainterPath path; - path.addRoundedRect(rect(), corner_radius_, corner_radius_); - - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); - - state_machine_->setupProperties(); - state_machine_->startAnimations(); -} - -FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset) - : QPushButton(parent) -{ - init(); - applyPreset(preset); -} - -FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset) - : QPushButton(text, parent) -{ - init(); - applyPreset(preset); -} - -FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset) - : QPushButton(text, parent) -{ - init(); - applyPreset(preset); - setRole(role); -} - -FlatButton::~FlatButton() {} - -void -FlatButton::applyPreset(ui::ButtonPreset preset) -{ - switch (preset) { - case ui::ButtonPreset::FlatPreset: - setOverlayStyle(ui::OverlayStyle::NoOverlay); - break; - case ui::ButtonPreset::CheckablePreset: - setOverlayStyle(ui::OverlayStyle::NoOverlay); - setCheckable(true); - break; - default: - break; - } -} - -void -FlatButton::setRole(ui::Role role) -{ - role_ = role; - state_machine_->setupProperties(); -} - -ui::Role -FlatButton::role() const -{ - return role_; -} - -void -FlatButton::setForegroundColor(const QColor &color) -{ - foreground_color_ = color; -} - -QColor -FlatButton::foregroundColor() const -{ - if (!foreground_color_.isValid()) { - if (bg_mode_ == Qt::OpaqueMode) { - return ThemeManager::instance().themeColor("BrightWhite"); - } - - switch (role_) { - case ui::Role::Primary: - return ThemeManager::instance().themeColor("Blue"); - case ui::Role::Secondary: - return ThemeManager::instance().themeColor("Gray"); - case ui::Role::Default: - default: - return ThemeManager::instance().themeColor("Black"); - } - } - - return foreground_color_; -} - -void -FlatButton::setBackgroundColor(const QColor &color) -{ - background_color_ = color; -} - -QColor -FlatButton::backgroundColor() const -{ - if (!background_color_.isValid()) { - switch (role_) { - case ui::Role::Primary: - return ThemeManager::instance().themeColor("Blue"); - case ui::Role::Secondary: - return ThemeManager::instance().themeColor("Gray"); - case ui::Role::Default: - default: - return ThemeManager::instance().themeColor("Black"); - } - } - - return background_color_; -} - -void -FlatButton::setOverlayColor(const QColor &color) -{ - overlay_color_ = color; - setOverlayStyle(ui::OverlayStyle::TintedOverlay); -} - -QColor -FlatButton::overlayColor() const -{ - if (!overlay_color_.isValid()) { - return foregroundColor(); - } - - return overlay_color_; -} - -void -FlatButton::setDisabledForegroundColor(const QColor &color) -{ - disabled_color_ = color; -} - -QColor -FlatButton::disabledForegroundColor() const -{ - if (!disabled_color_.isValid()) { - return ThemeManager::instance().themeColor("FadedWhite"); - } - - return disabled_color_; -} - -void -FlatButton::setDisabledBackgroundColor(const QColor &color) -{ - disabled_background_color_ = color; -} - -QColor -FlatButton::disabledBackgroundColor() const -{ - if (!disabled_background_color_.isValid()) { - return ThemeManager::instance().themeColor("FadedWhite"); - } - - return disabled_background_color_; -} - -void -FlatButton::setFontSize(qreal size) -{ - font_size_ = size; - - QFont f(font()); - f.setPixelSize(size); - setFont(f); - - update(); -} - -qreal -FlatButton::fontSize() const -{ - return font_size_; -} - -void -FlatButton::setOverlayStyle(ui::OverlayStyle style) -{ - overlay_style_ = style; - update(); -} - -ui::OverlayStyle -FlatButton::overlayStyle() const -{ - return overlay_style_; -} - -void -FlatButton::setRippleStyle(ui::RippleStyle style) -{ - ripple_style_ = style; -} - -ui::RippleStyle -FlatButton::rippleStyle() const -{ - return ripple_style_; -} - -void -FlatButton::setIconPlacement(ui::ButtonIconPlacement placement) -{ - icon_placement_ = placement; - update(); -} - -ui::ButtonIconPlacement -FlatButton::iconPlacement() const -{ - return icon_placement_; -} - -void -FlatButton::setCornerRadius(qreal radius) -{ - corner_radius_ = radius; - updateClipPath(); - update(); -} - -qreal -FlatButton::cornerRadius() const -{ - return corner_radius_; -} - -void -FlatButton::setBackgroundMode(Qt::BGMode mode) -{ - bg_mode_ = mode; - state_machine_->setupProperties(); -} - -Qt::BGMode -FlatButton::backgroundMode() const -{ - return bg_mode_; -} - -void -FlatButton::setBaseOpacity(qreal opacity) -{ - base_opacity_ = opacity; - state_machine_->setupProperties(); -} - -qreal -FlatButton::baseOpacity() const -{ - return base_opacity_; -} - -void -FlatButton::setCheckable(bool value) -{ - state_machine_->updateCheckedStatus(); - state_machine_->setCheckedOverlayProgress(0); - - QPushButton::setCheckable(value); -} - -void -FlatButton::setHasFixedRippleRadius(bool value) -{ - use_fixed_ripple_radius_ = value; -} - -bool -FlatButton::hasFixedRippleRadius() const -{ - return use_fixed_ripple_radius_; -} - -void -FlatButton::setFixedRippleRadius(qreal radius) -{ - fixed_ripple_radius_ = radius; - setHasFixedRippleRadius(true); -} - -QSize -FlatButton::sizeHint() const -{ - ensurePolished(); - - QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); - - int w = 20 + label.width(); - int h = label.height(); - - if (!icon().isNull()) { - w += iconSize().width() + FlatButton::IconPadding; - h = qMax(h, iconSize().height()); - } - - return QSize(w, 20 + h); -} - -void -FlatButton::checkStateSet() -{ - state_machine_->updateCheckedStatus(); - QPushButton::checkStateSet(); -} - -void -FlatButton::mousePressEvent(QMouseEvent *event) -{ - if (ui::RippleStyle::NoRipple != ripple_style_) { - QPoint pos; - qreal radiusEndValue; - - if (ui::RippleStyle::CenteredRipple == ripple_style_) { - pos = rect().center(); - } else { - pos = event->pos(); - } - - if (use_fixed_ripple_radius_) { - radiusEndValue = fixed_ripple_radius_; - } else { - radiusEndValue = static_cast(width()) / 2; - } - - Ripple *ripple = new Ripple(pos); - - ripple->setRadiusEndValue(radiusEndValue); - ripple->setOpacityStartValue(0.35); - ripple->setColor(foregroundColor()); - ripple->radiusAnimation()->setDuration(250); - ripple->opacityAnimation()->setDuration(250); - - ripple_overlay_->addRipple(ripple); - } - - QPushButton::mousePressEvent(event); -} - -void -FlatButton::mouseReleaseEvent(QMouseEvent *event) -{ - QPushButton::mouseReleaseEvent(event); - state_machine_->updateCheckedStatus(); -} - -void -FlatButton::resizeEvent(QResizeEvent *event) -{ - QPushButton::resizeEvent(event); - updateClipPath(); -} - -void -FlatButton::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - const qreal cr = corner_radius_; - - if (cr > 0) { - QPainterPath path; - path.addRoundedRect(rect(), cr, cr); - - painter.setClipPath(path); - painter.setClipping(true); - } - - paintBackground(&painter); - - painter.setOpacity(1); - painter.setClipping(false); - - paintForeground(&painter); -} - -void -FlatButton::paintBackground(QPainter *painter) -{ - const qreal overlayOpacity = state_machine_->overlayOpacity(); - const qreal checkedProgress = state_machine_->checkedOverlayProgress(); - - if (Qt::OpaqueMode == bg_mode_) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - - if (isEnabled()) { - brush.setColor(backgroundColor()); - } else { - brush.setColor(disabledBackgroundColor()); - } - - painter->setOpacity(1); - painter->setBrush(brush); - painter->setPen(Qt::NoPen); - painter->drawRect(rect()); - } - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - painter->setPen(Qt::NoPen); - - if (!isEnabled()) { - return; - } - - if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) { - if (ui::OverlayStyle::TintedOverlay == overlay_style_) { - brush.setColor(overlayColor()); - } else { - brush.setColor(Qt::gray); - } - - painter->setOpacity(overlayOpacity); - painter->setBrush(brush); - painter->drawRect(rect()); - } - - if (isCheckable() && checkedProgress > 0) { - const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7; - brush.setColor(foregroundColor()); - painter->setOpacity(q * checkedProgress); - painter->setBrush(brush); - QRect r(rect()); - r.setHeight(static_cast(r.height()) * checkedProgress); - painter->drawRect(r); - } -} - -#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH() - -void -FlatButton::paintForeground(QPainter *painter) -{ - if (isEnabled()) { - painter->setPen(foregroundColor()); - const qreal progress = state_machine_->checkedOverlayProgress(); - - if (isCheckable() && progress > 0) { - QColor source = foregroundColor(); - QColor dest = - Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor(); - if (qFuzzyCompare(1, progress)) { - painter->setPen(dest); - } else { - painter->setPen(QColor(COLOR_INTERPOLATE(red), - COLOR_INTERPOLATE(green), - COLOR_INTERPOLATE(blue), - COLOR_INTERPOLATE(alpha))); - } - } - } else { - painter->setPen(disabledForegroundColor()); - } - - if (icon().isNull()) { - painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text())); - return; - } - - QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); - QSize base(size() - textSize); - - const int iw = iconSize().width() + IconPadding; - QPoint pos((base.width() - iw) / 2, 0); - - QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize); - QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize()); - - /* if (ui::LeftIcon == icon_placement_) { */ - /* textGeometry.translate(iw, 0); */ - /* } else { */ - /* iconGeometry.translate(textSize.width() + IconPadding, 0); */ - /* } */ - - painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text())); - - QPixmap pixmap = icon().pixmap(iconSize()); - QPainter icon(&pixmap); - icon.setCompositionMode(QPainter::CompositionMode_SourceIn); - icon.fillRect(pixmap.rect(), painter->pen().color()); - painter->drawPixmap(iconGeometry, pixmap); -} - -void -FlatButton::updateClipPath() -{ - const qreal radius = corner_radius_; - - QPainterPath path; - path.addRoundedRect(rect(), radius, radius); - ripple_overlay_->setClipPath(path); -} - -FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent) - : QStateMachine(parent) - , button_(parent) - , top_level_state_(new QState(QState::ParallelStates)) - , config_state_(new QState(top_level_state_)) - , checkable_state_(new QState(top_level_state_)) - , checked_state_(new QState(checkable_state_)) - , unchecked_state_(new QState(checkable_state_)) - , neutral_state_(new QState(config_state_)) - , neutral_focused_state_(new QState(config_state_)) - , hovered_state_(new QState(config_state_)) - , hovered_focused_state_(new QState(config_state_)) - , pressed_state_(new QState(config_state_)) - , overlay_opacity_(0) - , checked_overlay_progress_(parent->isChecked() ? 1 : 0) - , was_checked_(false) -{ - Q_ASSERT(parent); - - parent->installEventFilter(this); - - config_state_->setInitialState(neutral_state_); - addState(top_level_state_); - setInitialState(top_level_state_); - - checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_); - QSignalTransition *transition; - QPropertyAnimation *animation; - - transition = new QSignalTransition(this, SIGNAL(buttonChecked())); - transition->setTargetState(checked_state_); - unchecked_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); - animation->setDuration(200); - transition->addAnimation(animation); - - transition = new QSignalTransition(this, SIGNAL(buttonUnchecked())); - transition->setTargetState(unchecked_state_); - checked_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); - animation->setDuration(200); - transition->addAnimation(animation); - - addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_); - addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_); - addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_); - addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_); - addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_); - addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_); - addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_); - addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_); -} - -FlatButtonStateMachine::~FlatButtonStateMachine() {} - -void -FlatButtonStateMachine::setOverlayOpacity(qreal opacity) -{ - overlay_opacity_ = opacity; - button_->update(); -} - -void -FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity) -{ - checked_overlay_progress_ = opacity; - button_->update(); -} - -void -FlatButtonStateMachine::startAnimations() -{ - start(); -} - -void -FlatButtonStateMachine::setupProperties() -{ - QColor overlayColor; - - if (Qt::TransparentMode == button_->backgroundMode()) { - overlayColor = button_->backgroundColor(); - } else { - overlayColor = button_->foregroundColor(); - } - - const qreal baseOpacity = button_->baseOpacity(); - - neutral_state_->assignProperty(this, "overlayOpacity", 0); - neutral_focused_state_->assignProperty(this, "overlayOpacity", 0); - hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity); - hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity); - pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity); - checked_state_->assignProperty(this, "checkedOverlayProgress", 1); - unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0); - - button_->update(); -} - -void -FlatButtonStateMachine::updateCheckedStatus() -{ - const bool checked = button_->isChecked(); - if (was_checked_ != checked) { - was_checked_ = checked; - if (checked) { - emit buttonChecked(); - } else { - emit buttonUnchecked(); - } - } -} - -bool -FlatButtonStateMachine::eventFilter(QObject *watched, QEvent *event) -{ - if (QEvent::FocusIn == event->type()) { - QFocusEvent *focusEvent = static_cast(event); - if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) { - emit buttonPressed(); - return true; - } - } - - return QStateMachine::eventFilter(watched, event); -} - -void -FlatButtonStateMachine::addTransition(QObject *object, - const char *signal, - QState *fromState, - QState *toState) -{ - addTransition(new QSignalTransition(object, signal), fromState, toState); -} - -void -FlatButtonStateMachine::addTransition(QObject *object, - QEvent::Type eventType, - QState *fromState, - QState *toState) -{ - addTransition(new QEventTransition(object, eventType), fromState, toState); -} - -void -FlatButtonStateMachine::addTransition(QAbstractTransition *transition, - QState *fromState, - QState *toState) -{ - transition->setTargetState(toState); - - QPropertyAnimation *animation; - - animation = new QPropertyAnimation(this, "overlayOpacity", this); - animation->setDuration(150); - transition->addAnimation(animation); - - fromState->addTransition(transition); -} diff --git a/src/ui/FlatButton.cpp b/src/ui/FlatButton.cpp new file mode 100644 index 00000000..45a7683e --- /dev/null +++ b/src/ui/FlatButton.cpp @@ -0,0 +1,719 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "FlatButton.h" +#include "Ripple.h" +#include "RippleOverlay.h" +#include "ThemeManager.h" + +// The ampersand is automatically set in QPushButton or QCheckbx +// by KDEPlatformTheme plugin in Qt5. +// [https://bugs.kde.org/show_bug.cgi?id=337491] +// +// A workaroud is to add +// +// [Development] +// AutoCheckAccelerators=false +// +// to ~/.config/kdeglobals +static QString +removeKDEAccelerators(QString text) +{ + return text.remove(QChar('&')); +} + +void +FlatButton::init() +{ + ripple_overlay_ = new RippleOverlay(this); + state_machine_ = new FlatButtonStateMachine(this); + role_ = ui::Role::Default; + ripple_style_ = ui::RippleStyle::PositionedRipple; + icon_placement_ = ui::ButtonIconPlacement::LeftIcon; + overlay_style_ = ui::OverlayStyle::GrayOverlay; + bg_mode_ = Qt::TransparentMode; + fixed_ripple_radius_ = 64; + corner_radius_ = 3; + base_opacity_ = 0.13; + font_size_ = 10; // 10.5; + use_fixed_ripple_radius_ = false; + + setStyle(&ThemeManager::instance()); + setAttribute(Qt::WA_Hover); + setMouseTracking(true); + setCursor(QCursor(Qt::PointingHandCursor)); + + QPainterPath path; + path.addRoundedRect(rect(), corner_radius_, corner_radius_); + + ripple_overlay_->setClipPath(path); + ripple_overlay_->setClipping(true); + + state_machine_->setupProperties(); + state_machine_->startAnimations(); +} + +FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset) + : QPushButton(parent) +{ + init(); + applyPreset(preset); +} + +FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset) + : QPushButton(text, parent) +{ + init(); + applyPreset(preset); +} + +FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset) + : QPushButton(text, parent) +{ + init(); + applyPreset(preset); + setRole(role); +} + +FlatButton::~FlatButton() {} + +void +FlatButton::applyPreset(ui::ButtonPreset preset) +{ + switch (preset) { + case ui::ButtonPreset::FlatPreset: + setOverlayStyle(ui::OverlayStyle::NoOverlay); + break; + case ui::ButtonPreset::CheckablePreset: + setOverlayStyle(ui::OverlayStyle::NoOverlay); + setCheckable(true); + break; + default: + break; + } +} + +void +FlatButton::setRole(ui::Role role) +{ + role_ = role; + state_machine_->setupProperties(); +} + +ui::Role +FlatButton::role() const +{ + return role_; +} + +void +FlatButton::setForegroundColor(const QColor &color) +{ + foreground_color_ = color; +} + +QColor +FlatButton::foregroundColor() const +{ + if (!foreground_color_.isValid()) { + if (bg_mode_ == Qt::OpaqueMode) { + return ThemeManager::instance().themeColor("BrightWhite"); + } + + switch (role_) { + case ui::Role::Primary: + return ThemeManager::instance().themeColor("Blue"); + case ui::Role::Secondary: + return ThemeManager::instance().themeColor("Gray"); + case ui::Role::Default: + default: + return ThemeManager::instance().themeColor("Black"); + } + } + + return foreground_color_; +} + +void +FlatButton::setBackgroundColor(const QColor &color) +{ + background_color_ = color; +} + +QColor +FlatButton::backgroundColor() const +{ + if (!background_color_.isValid()) { + switch (role_) { + case ui::Role::Primary: + return ThemeManager::instance().themeColor("Blue"); + case ui::Role::Secondary: + return ThemeManager::instance().themeColor("Gray"); + case ui::Role::Default: + default: + return ThemeManager::instance().themeColor("Black"); + } + } + + return background_color_; +} + +void +FlatButton::setOverlayColor(const QColor &color) +{ + overlay_color_ = color; + setOverlayStyle(ui::OverlayStyle::TintedOverlay); +} + +QColor +FlatButton::overlayColor() const +{ + if (!overlay_color_.isValid()) { + return foregroundColor(); + } + + return overlay_color_; +} + +void +FlatButton::setDisabledForegroundColor(const QColor &color) +{ + disabled_color_ = color; +} + +QColor +FlatButton::disabledForegroundColor() const +{ + if (!disabled_color_.isValid()) { + return ThemeManager::instance().themeColor("FadedWhite"); + } + + return disabled_color_; +} + +void +FlatButton::setDisabledBackgroundColor(const QColor &color) +{ + disabled_background_color_ = color; +} + +QColor +FlatButton::disabledBackgroundColor() const +{ + if (!disabled_background_color_.isValid()) { + return ThemeManager::instance().themeColor("FadedWhite"); + } + + return disabled_background_color_; +} + +void +FlatButton::setFontSize(qreal size) +{ + font_size_ = size; + + QFont f(font()); + f.setPixelSize(size); + setFont(f); + + update(); +} + +qreal +FlatButton::fontSize() const +{ + return font_size_; +} + +void +FlatButton::setOverlayStyle(ui::OverlayStyle style) +{ + overlay_style_ = style; + update(); +} + +ui::OverlayStyle +FlatButton::overlayStyle() const +{ + return overlay_style_; +} + +void +FlatButton::setRippleStyle(ui::RippleStyle style) +{ + ripple_style_ = style; +} + +ui::RippleStyle +FlatButton::rippleStyle() const +{ + return ripple_style_; +} + +void +FlatButton::setIconPlacement(ui::ButtonIconPlacement placement) +{ + icon_placement_ = placement; + update(); +} + +ui::ButtonIconPlacement +FlatButton::iconPlacement() const +{ + return icon_placement_; +} + +void +FlatButton::setCornerRadius(qreal radius) +{ + corner_radius_ = radius; + updateClipPath(); + update(); +} + +qreal +FlatButton::cornerRadius() const +{ + return corner_radius_; +} + +void +FlatButton::setBackgroundMode(Qt::BGMode mode) +{ + bg_mode_ = mode; + state_machine_->setupProperties(); +} + +Qt::BGMode +FlatButton::backgroundMode() const +{ + return bg_mode_; +} + +void +FlatButton::setBaseOpacity(qreal opacity) +{ + base_opacity_ = opacity; + state_machine_->setupProperties(); +} + +qreal +FlatButton::baseOpacity() const +{ + return base_opacity_; +} + +void +FlatButton::setCheckable(bool value) +{ + state_machine_->updateCheckedStatus(); + state_machine_->setCheckedOverlayProgress(0); + + QPushButton::setCheckable(value); +} + +void +FlatButton::setHasFixedRippleRadius(bool value) +{ + use_fixed_ripple_radius_ = value; +} + +bool +FlatButton::hasFixedRippleRadius() const +{ + return use_fixed_ripple_radius_; +} + +void +FlatButton::setFixedRippleRadius(qreal radius) +{ + fixed_ripple_radius_ = radius; + setHasFixedRippleRadius(true); +} + +QSize +FlatButton::sizeHint() const +{ + ensurePolished(); + + QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); + + int w = 20 + label.width(); + int h = label.height(); + + if (!icon().isNull()) { + w += iconSize().width() + FlatButton::IconPadding; + h = qMax(h, iconSize().height()); + } + + return QSize(w, 20 + h); +} + +void +FlatButton::checkStateSet() +{ + state_machine_->updateCheckedStatus(); + QPushButton::checkStateSet(); +} + +void +FlatButton::mousePressEvent(QMouseEvent *event) +{ + if (ui::RippleStyle::NoRipple != ripple_style_) { + QPoint pos; + qreal radiusEndValue; + + if (ui::RippleStyle::CenteredRipple == ripple_style_) { + pos = rect().center(); + } else { + pos = event->pos(); + } + + if (use_fixed_ripple_radius_) { + radiusEndValue = fixed_ripple_radius_; + } else { + radiusEndValue = static_cast(width()) / 2; + } + + Ripple *ripple = new Ripple(pos); + + ripple->setRadiusEndValue(radiusEndValue); + ripple->setOpacityStartValue(0.35); + ripple->setColor(foregroundColor()); + ripple->radiusAnimation()->setDuration(250); + ripple->opacityAnimation()->setDuration(250); + + ripple_overlay_->addRipple(ripple); + } + + QPushButton::mousePressEvent(event); +} + +void +FlatButton::mouseReleaseEvent(QMouseEvent *event) +{ + QPushButton::mouseReleaseEvent(event); + state_machine_->updateCheckedStatus(); +} + +void +FlatButton::resizeEvent(QResizeEvent *event) +{ + QPushButton::resizeEvent(event); + updateClipPath(); +} + +void +FlatButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + const qreal cr = corner_radius_; + + if (cr > 0) { + QPainterPath path; + path.addRoundedRect(rect(), cr, cr); + + painter.setClipPath(path); + painter.setClipping(true); + } + + paintBackground(&painter); + + painter.setOpacity(1); + painter.setClipping(false); + + paintForeground(&painter); +} + +void +FlatButton::paintBackground(QPainter *painter) +{ + const qreal overlayOpacity = state_machine_->overlayOpacity(); + const qreal checkedProgress = state_machine_->checkedOverlayProgress(); + + if (Qt::OpaqueMode == bg_mode_) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + + if (isEnabled()) { + brush.setColor(backgroundColor()); + } else { + brush.setColor(disabledBackgroundColor()); + } + + painter->setOpacity(1); + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->drawRect(rect()); + } + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + painter->setPen(Qt::NoPen); + + if (!isEnabled()) { + return; + } + + if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) { + if (ui::OverlayStyle::TintedOverlay == overlay_style_) { + brush.setColor(overlayColor()); + } else { + brush.setColor(Qt::gray); + } + + painter->setOpacity(overlayOpacity); + painter->setBrush(brush); + painter->drawRect(rect()); + } + + if (isCheckable() && checkedProgress > 0) { + const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7; + brush.setColor(foregroundColor()); + painter->setOpacity(q * checkedProgress); + painter->setBrush(brush); + QRect r(rect()); + r.setHeight(static_cast(r.height()) * checkedProgress); + painter->drawRect(r); + } +} + +#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH() + +void +FlatButton::paintForeground(QPainter *painter) +{ + if (isEnabled()) { + painter->setPen(foregroundColor()); + const qreal progress = state_machine_->checkedOverlayProgress(); + + if (isCheckable() && progress > 0) { + QColor source = foregroundColor(); + QColor dest = + Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor(); + if (qFuzzyCompare(1, progress)) { + painter->setPen(dest); + } else { + painter->setPen(QColor(COLOR_INTERPOLATE(red), + COLOR_INTERPOLATE(green), + COLOR_INTERPOLATE(blue), + COLOR_INTERPOLATE(alpha))); + } + } + } else { + painter->setPen(disabledForegroundColor()); + } + + if (icon().isNull()) { + painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text())); + return; + } + + QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); + QSize base(size() - textSize); + + const int iw = iconSize().width() + IconPadding; + QPoint pos((base.width() - iw) / 2, 0); + + QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize); + QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize()); + + /* if (ui::LeftIcon == icon_placement_) { */ + /* textGeometry.translate(iw, 0); */ + /* } else { */ + /* iconGeometry.translate(textSize.width() + IconPadding, 0); */ + /* } */ + + painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text())); + + QPixmap pixmap = icon().pixmap(iconSize()); + QPainter icon(&pixmap); + icon.setCompositionMode(QPainter::CompositionMode_SourceIn); + icon.fillRect(pixmap.rect(), painter->pen().color()); + painter->drawPixmap(iconGeometry, pixmap); +} + +void +FlatButton::updateClipPath() +{ + const qreal radius = corner_radius_; + + QPainterPath path; + path.addRoundedRect(rect(), radius, radius); + ripple_overlay_->setClipPath(path); +} + +FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent) + : QStateMachine(parent) + , button_(parent) + , top_level_state_(new QState(QState::ParallelStates)) + , config_state_(new QState(top_level_state_)) + , checkable_state_(new QState(top_level_state_)) + , checked_state_(new QState(checkable_state_)) + , unchecked_state_(new QState(checkable_state_)) + , neutral_state_(new QState(config_state_)) + , neutral_focused_state_(new QState(config_state_)) + , hovered_state_(new QState(config_state_)) + , hovered_focused_state_(new QState(config_state_)) + , pressed_state_(new QState(config_state_)) + , overlay_opacity_(0) + , checked_overlay_progress_(parent->isChecked() ? 1 : 0) + , was_checked_(false) +{ + Q_ASSERT(parent); + + parent->installEventFilter(this); + + config_state_->setInitialState(neutral_state_); + addState(top_level_state_); + setInitialState(top_level_state_); + + checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_); + QSignalTransition *transition; + QPropertyAnimation *animation; + + transition = new QSignalTransition(this, SIGNAL(buttonChecked())); + transition->setTargetState(checked_state_); + unchecked_state_->addTransition(transition); + + animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); + animation->setDuration(200); + transition->addAnimation(animation); + + transition = new QSignalTransition(this, SIGNAL(buttonUnchecked())); + transition->setTargetState(unchecked_state_); + checked_state_->addTransition(transition); + + animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); + animation->setDuration(200); + transition->addAnimation(animation); + + addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_); + addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_); + addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_); + addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_); + addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_); + addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_); + addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_); + addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_); + addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_); + addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_); + addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_); +} + +FlatButtonStateMachine::~FlatButtonStateMachine() {} + +void +FlatButtonStateMachine::setOverlayOpacity(qreal opacity) +{ + overlay_opacity_ = opacity; + button_->update(); +} + +void +FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity) +{ + checked_overlay_progress_ = opacity; + button_->update(); +} + +void +FlatButtonStateMachine::startAnimations() +{ + start(); +} + +void +FlatButtonStateMachine::setupProperties() +{ + QColor overlayColor; + + if (Qt::TransparentMode == button_->backgroundMode()) { + overlayColor = button_->backgroundColor(); + } else { + overlayColor = button_->foregroundColor(); + } + + const qreal baseOpacity = button_->baseOpacity(); + + neutral_state_->assignProperty(this, "overlayOpacity", 0); + neutral_focused_state_->assignProperty(this, "overlayOpacity", 0); + hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity); + hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity); + pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity); + checked_state_->assignProperty(this, "checkedOverlayProgress", 1); + unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0); + + button_->update(); +} + +void +FlatButtonStateMachine::updateCheckedStatus() +{ + const bool checked = button_->isChecked(); + if (was_checked_ != checked) { + was_checked_ = checked; + if (checked) { + emit buttonChecked(); + } else { + emit buttonUnchecked(); + } + } +} + +bool +FlatButtonStateMachine::eventFilter(QObject *watched, QEvent *event) +{ + if (QEvent::FocusIn == event->type()) { + QFocusEvent *focusEvent = static_cast(event); + if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) { + emit buttonPressed(); + return true; + } + } + + return QStateMachine::eventFilter(watched, event); +} + +void +FlatButtonStateMachine::addTransition(QObject *object, + const char *signal, + QState *fromState, + QState *toState) +{ + addTransition(new QSignalTransition(object, signal), fromState, toState); +} + +void +FlatButtonStateMachine::addTransition(QObject *object, + QEvent::Type eventType, + QState *fromState, + QState *toState) +{ + addTransition(new QEventTransition(object, eventType), fromState, toState); +} + +void +FlatButtonStateMachine::addTransition(QAbstractTransition *transition, + QState *fromState, + QState *toState) +{ + transition->setTargetState(toState); + + QPropertyAnimation *animation; + + animation = new QPropertyAnimation(this, "overlayOpacity", this); + animation->setDuration(150); + transition->addAnimation(animation); + + fromState->addTransition(transition); +} diff --git a/src/ui/FlatButton.h b/src/ui/FlatButton.h new file mode 100644 index 00000000..9c2bf425 --- /dev/null +++ b/src/ui/FlatButton.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include + +#include "Theme.h" + +class RippleOverlay; +class FlatButton; + +class FlatButtonStateMachine : public QStateMachine +{ + Q_OBJECT + + Q_PROPERTY(qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity) + Q_PROPERTY( + qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ checkedOverlayProgress) + +public: + explicit FlatButtonStateMachine(FlatButton *parent); + ~FlatButtonStateMachine(); + + void setOverlayOpacity(qreal opacity); + void setCheckedOverlayProgress(qreal opacity); + + inline qreal overlayOpacity() const; + inline qreal checkedOverlayProgress() const; + + void startAnimations(); + void setupProperties(); + void updateCheckedStatus(); + +signals: + void buttonPressed(); + void buttonChecked(); + void buttonUnchecked(); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState); + void addTransition(QObject *object, + QEvent::Type eventType, + QState *fromState, + QState *toState); + void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState); + + FlatButton *const button_; + + QState *const top_level_state_; + QState *const config_state_; + QState *const checkable_state_; + QState *const checked_state_; + QState *const unchecked_state_; + QState *const neutral_state_; + QState *const neutral_focused_state_; + QState *const hovered_state_; + QState *const hovered_focused_state_; + QState *const pressed_state_; + + qreal overlay_opacity_; + qreal checked_overlay_progress_; + + bool was_checked_; +}; + +inline qreal +FlatButtonStateMachine::overlayOpacity() const +{ + return overlay_opacity_; +} + +inline qreal +FlatButtonStateMachine::checkedOverlayProgress() const +{ + return checked_overlay_progress_; +} + +class FlatButton : public QPushButton +{ + Q_OBJECT + + Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor) + Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) + Q_PROPERTY(QColor overlayColor WRITE setOverlayColor READ overlayColor) + Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ + disabledForegroundColor) + Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ + disabledBackgroundColor) + Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize) + +public: + explicit FlatButton(QWidget *parent = 0, + ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); + explicit FlatButton(const QString &text, + QWidget *parent = 0, + ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); + FlatButton(const QString &text, + ui::Role role, + QWidget *parent = 0, + ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); + ~FlatButton(); + + void applyPreset(ui::ButtonPreset preset); + + void setBackgroundColor(const QColor &color); + void setBackgroundMode(Qt::BGMode mode); + void setBaseOpacity(qreal opacity); + void setCheckable(bool value); + void setCornerRadius(qreal radius); + void setDisabledBackgroundColor(const QColor &color); + void setDisabledForegroundColor(const QColor &color); + void setFixedRippleRadius(qreal radius); + void setFontSize(qreal size); + void setForegroundColor(const QColor &color); + void setHasFixedRippleRadius(bool value); + void setIconPlacement(ui::ButtonIconPlacement placement); + void setOverlayColor(const QColor &color); + void setOverlayStyle(ui::OverlayStyle style); + void setRippleStyle(ui::RippleStyle style); + void setRole(ui::Role role); + + QColor foregroundColor() const; + QColor backgroundColor() const; + QColor overlayColor() const; + QColor disabledForegroundColor() const; + QColor disabledBackgroundColor() const; + + qreal fontSize() const; + qreal cornerRadius() const; + qreal baseOpacity() const; + + bool hasFixedRippleRadius() const; + + ui::Role role() const; + ui::OverlayStyle overlayStyle() const; + ui::RippleStyle rippleStyle() const; + ui::ButtonIconPlacement iconPlacement() const; + + Qt::BGMode backgroundMode() const; + + QSize sizeHint() const override; + +protected: + int IconPadding = 0; + + void checkStateSet() override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + + virtual void paintBackground(QPainter *painter); + virtual void paintForeground(QPainter *painter); + virtual void updateClipPath(); + + void init(); + +private: + RippleOverlay *ripple_overlay_; + FlatButtonStateMachine *state_machine_; + + ui::Role role_; + ui::RippleStyle ripple_style_; + ui::ButtonIconPlacement icon_placement_; + ui::OverlayStyle overlay_style_; + + Qt::BGMode bg_mode_; + + QColor background_color_; + QColor foreground_color_; + QColor overlay_color_; + QColor disabled_color_; + QColor disabled_background_color_; + + qreal fixed_ripple_radius_; + qreal corner_radius_; + qreal base_opacity_; + qreal font_size_; + + bool use_fixed_ripple_radius_; +}; diff --git a/src/ui/FloatingButton.cc b/src/ui/FloatingButton.cc deleted file mode 100644 index 74dcd482..00000000 --- a/src/ui/FloatingButton.cc +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include "FloatingButton.h" - -FloatingButton::FloatingButton(const QIcon &icon, QWidget *parent) - : RaisedButton(parent) -{ - setFixedSize(DIAMETER, DIAMETER); - setGeometry(buttonGeometry()); - - if (parentWidget()) - parentWidget()->installEventFilter(this); - - setFixedRippleRadius(50); - setIcon(icon); - raise(); -} - -QRect -FloatingButton::buttonGeometry() const -{ - QWidget *parent = parentWidget(); - - if (!parent) - return QRect(); - - return QRect(parent->width() - (OFFSET_X + DIAMETER), - parent->height() - (OFFSET_Y + DIAMETER), - DIAMETER, - DIAMETER); -} - -bool -FloatingButton::event(QEvent *event) -{ - if (!parent()) - return RaisedButton::event(event); - - switch (event->type()) { - case QEvent::ParentChange: { - parent()->installEventFilter(this); - setGeometry(buttonGeometry()); - break; - } - case QEvent::ParentAboutToChange: { - parent()->installEventFilter(this); - break; - } - default: - break; - } - - return RaisedButton::event(event); -} - -bool -FloatingButton::eventFilter(QObject *obj, QEvent *event) -{ - const QEvent::Type type = event->type(); - - if (QEvent::Move == type || QEvent::Resize == type) - setGeometry(buttonGeometry()); - - return RaisedButton::eventFilter(obj, event); -} - -void -FloatingButton::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QRect square = QRect(0, 0, DIAMETER, DIAMETER); - square.moveCenter(rect().center()); - - QPainter p(this); - p.setRenderHints(QPainter::Antialiasing); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(backgroundColor()); - - p.setBrush(brush); - p.setPen(Qt::NoPen); - p.drawEllipse(square); - - QRect iconGeometry(0, 0, ICON_SIZE, ICON_SIZE); - iconGeometry.moveCenter(square.center()); - - QPixmap pixmap = icon().pixmap(QSize(ICON_SIZE, ICON_SIZE)); - QPainter icon(&pixmap); - icon.setCompositionMode(QPainter::CompositionMode_SourceIn); - icon.fillRect(pixmap.rect(), foregroundColor()); - - p.drawPixmap(iconGeometry, pixmap); -} diff --git a/src/ui/FloatingButton.cpp b/src/ui/FloatingButton.cpp new file mode 100644 index 00000000..74dcd482 --- /dev/null +++ b/src/ui/FloatingButton.cpp @@ -0,0 +1,95 @@ +#include + +#include "FloatingButton.h" + +FloatingButton::FloatingButton(const QIcon &icon, QWidget *parent) + : RaisedButton(parent) +{ + setFixedSize(DIAMETER, DIAMETER); + setGeometry(buttonGeometry()); + + if (parentWidget()) + parentWidget()->installEventFilter(this); + + setFixedRippleRadius(50); + setIcon(icon); + raise(); +} + +QRect +FloatingButton::buttonGeometry() const +{ + QWidget *parent = parentWidget(); + + if (!parent) + return QRect(); + + return QRect(parent->width() - (OFFSET_X + DIAMETER), + parent->height() - (OFFSET_Y + DIAMETER), + DIAMETER, + DIAMETER); +} + +bool +FloatingButton::event(QEvent *event) +{ + if (!parent()) + return RaisedButton::event(event); + + switch (event->type()) { + case QEvent::ParentChange: { + parent()->installEventFilter(this); + setGeometry(buttonGeometry()); + break; + } + case QEvent::ParentAboutToChange: { + parent()->installEventFilter(this); + break; + } + default: + break; + } + + return RaisedButton::event(event); +} + +bool +FloatingButton::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Move == type || QEvent::Resize == type) + setGeometry(buttonGeometry()); + + return RaisedButton::eventFilter(obj, event); +} + +void +FloatingButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QRect square = QRect(0, 0, DIAMETER, DIAMETER); + square.moveCenter(rect().center()); + + QPainter p(this); + p.setRenderHints(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor()); + + p.setBrush(brush); + p.setPen(Qt::NoPen); + p.drawEllipse(square); + + QRect iconGeometry(0, 0, ICON_SIZE, ICON_SIZE); + iconGeometry.moveCenter(square.center()); + + QPixmap pixmap = icon().pixmap(QSize(ICON_SIZE, ICON_SIZE)); + QPainter icon(&pixmap); + icon.setCompositionMode(QPainter::CompositionMode_SourceIn); + icon.fillRect(pixmap.rect(), foregroundColor()); + + p.drawPixmap(iconGeometry, pixmap); +} diff --git a/src/ui/FloatingButton.h b/src/ui/FloatingButton.h new file mode 100644 index 00000000..91e99ebb --- /dev/null +++ b/src/ui/FloatingButton.h @@ -0,0 +1,26 @@ +#pragma once + +#include "RaisedButton.h" + +constexpr int DIAMETER = 40; +constexpr int ICON_SIZE = 18; + +constexpr int OFFSET_X = 30; +constexpr int OFFSET_Y = 20; + +class FloatingButton : public RaisedButton +{ + Q_OBJECT + +public: + FloatingButton(const QIcon &icon, QWidget *parent = nullptr); + + QSize sizeHint() const override { return QSize(DIAMETER, DIAMETER); }; + QRect buttonGeometry() const; + +protected: + bool event(QEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; + + void paintEvent(QPaintEvent *event) override; +}; diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp index b150e61b..3151bedf 100644 --- a/src/ui/InfoMessage.cpp +++ b/src/ui/InfoMessage.cpp @@ -1,5 +1,5 @@ +#include "InfoMessage.h" #include "Config.h" -#include "InfoMessage.hpp" #include #include diff --git a/src/ui/InfoMessage.h b/src/ui/InfoMessage.h new file mode 100644 index 00000000..58f98b0c --- /dev/null +++ b/src/ui/InfoMessage.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +class InfoMessage : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor) + +public: + explicit InfoMessage(QWidget *parent = nullptr); + InfoMessage(QString msg, QWidget *parent = nullptr); + + void setTextColor(QColor color) { textColor_ = color; } + void setBoxColor(QColor color) { boxColor_ = color; } + void saveDatetime(QDateTime datetime) { datetime_ = datetime; } + + QColor textColor() const { return textColor_; } + QColor boxColor() const { return boxColor_; } + QDateTime datetime() const { return datetime_; } + +protected: + void paintEvent(QPaintEvent *event) override; + + int width_; + int height_; + + QString msg_; + QFont font_; + + QDateTime datetime_; + + QColor textColor_ = QColor("black"); + QColor boxColor_ = QColor("white"); +}; + +class DateSeparator : public InfoMessage +{ + Q_OBJECT + +public: + DateSeparator(QDateTime datetime, QWidget *parent = nullptr); +}; diff --git a/src/ui/Label.cc b/src/ui/Label.cc deleted file mode 100644 index 8bd8c54e..00000000 --- a/src/ui/Label.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris - * - * This program 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. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "Label.h" -#include - -Label::Label(QWidget *parent, Qt::WindowFlags f) - : QLabel(parent, f) -{} - -Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f) - : QLabel(text, parent, f) -{} - -void -Label::mousePressEvent(QMouseEvent *e) -{ - pressPosition_ = e->pos(); - emit pressed(e); - QLabel::mousePressEvent(e); -} - -void -Label::mouseReleaseEvent(QMouseEvent *e) -{ - emit released(e); - if (pressPosition_ == e->pos()) - emit clicked(e); - QLabel::mouseReleaseEvent(e); -} diff --git a/src/ui/Label.cpp b/src/ui/Label.cpp new file mode 100644 index 00000000..8bd8c54e --- /dev/null +++ b/src/ui/Label.cpp @@ -0,0 +1,44 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Label.h" +#include + +Label::Label(QWidget *parent, Qt::WindowFlags f) + : QLabel(parent, f) +{} + +Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f) + : QLabel(text, parent, f) +{} + +void +Label::mousePressEvent(QMouseEvent *e) +{ + pressPosition_ = e->pos(); + emit pressed(e); + QLabel::mousePressEvent(e); +} + +void +Label::mouseReleaseEvent(QMouseEvent *e) +{ + emit released(e); + if (pressPosition_ == e->pos()) + emit clicked(e); + QLabel::mouseReleaseEvent(e); +} diff --git a/src/ui/Label.h b/src/ui/Label.h new file mode 100644 index 00000000..09cf27d7 --- /dev/null +++ b/src/ui/Label.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class Label : public QLabel +{ + Q_OBJECT + +public: + explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); + explicit Label(const QString &text, + QWidget *parent = Q_NULLPTR, + Qt::WindowFlags f = Qt::WindowFlags()); + +signals: + void clicked(QMouseEvent *e); + void pressed(QMouseEvent *e); + void released(QMouseEvent *e); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + + QPoint pressPosition_; +}; diff --git a/src/ui/LoadingIndicator.cc b/src/ui/LoadingIndicator.cc deleted file mode 100644 index f64151ce..00000000 --- a/src/ui/LoadingIndicator.cc +++ /dev/null @@ -1,85 +0,0 @@ -#include "LoadingIndicator.h" - -#include -#include - -LoadingIndicator::LoadingIndicator(QWidget *parent) - : QWidget(parent) - , interval_(70) - , angle_(0) - , color_(Qt::black) -{ - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setFocusPolicy(Qt::NoFocus); - - timer_ = new QTimer(); - connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout())); -} - -LoadingIndicator::~LoadingIndicator() -{ - stop(); - - delete timer_; -} - -void -LoadingIndicator::paintEvent(QPaintEvent *e) -{ - Q_UNUSED(e) - - if (!timer_->isActive()) - return; - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - int width = qMin(this->width(), this->height()); - - int outerRadius = (width - 4) * 0.5f; - int innerRadius = outerRadius * 0.78f; - - int capsuleRadius = (outerRadius - innerRadius) / 2; - - for (int i = 0; i < 8; ++i) { - QColor color = color_; - - color.setAlphaF(1.0f - (i / 8.0f)); - - painter.setPen(Qt::NoPen); - painter.setBrush(color); - - qreal radius = capsuleRadius * (1.0f - (i / 16.0f)); - - painter.save(); - - painter.translate(rect().center()); - painter.rotate(angle_ - i * 45.0f); - - QPointF center = QPointF(-capsuleRadius, -innerRadius); - painter.drawEllipse(center, radius * 2, radius * 2); - - painter.restore(); - } -} - -void -LoadingIndicator::start() -{ - timer_->start(interval_); - show(); -} - -void -LoadingIndicator::stop() -{ - timer_->stop(); - hide(); -} - -void -LoadingIndicator::onTimeout() -{ - angle_ = (angle_ + 45) % 360; - update(); -} diff --git a/src/ui/LoadingIndicator.cpp b/src/ui/LoadingIndicator.cpp new file mode 100644 index 00000000..f64151ce --- /dev/null +++ b/src/ui/LoadingIndicator.cpp @@ -0,0 +1,85 @@ +#include "LoadingIndicator.h" + +#include +#include + +LoadingIndicator::LoadingIndicator(QWidget *parent) + : QWidget(parent) + , interval_(70) + , angle_(0) + , color_(Qt::black) +{ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFocusPolicy(Qt::NoFocus); + + timer_ = new QTimer(); + connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout())); +} + +LoadingIndicator::~LoadingIndicator() +{ + stop(); + + delete timer_; +} + +void +LoadingIndicator::paintEvent(QPaintEvent *e) +{ + Q_UNUSED(e) + + if (!timer_->isActive()) + return; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + int width = qMin(this->width(), this->height()); + + int outerRadius = (width - 4) * 0.5f; + int innerRadius = outerRadius * 0.78f; + + int capsuleRadius = (outerRadius - innerRadius) / 2; + + for (int i = 0; i < 8; ++i) { + QColor color = color_; + + color.setAlphaF(1.0f - (i / 8.0f)); + + painter.setPen(Qt::NoPen); + painter.setBrush(color); + + qreal radius = capsuleRadius * (1.0f - (i / 16.0f)); + + painter.save(); + + painter.translate(rect().center()); + painter.rotate(angle_ - i * 45.0f); + + QPointF center = QPointF(-capsuleRadius, -innerRadius); + painter.drawEllipse(center, radius * 2, radius * 2); + + painter.restore(); + } +} + +void +LoadingIndicator::start() +{ + timer_->start(interval_); + show(); +} + +void +LoadingIndicator::stop() +{ + timer_->stop(); + hide(); +} + +void +LoadingIndicator::onTimeout() +{ + angle_ = (angle_ + 45) % 360; + update(); +} diff --git a/src/ui/LoadingIndicator.h b/src/ui/LoadingIndicator.h new file mode 100644 index 00000000..bb33fe6c --- /dev/null +++ b/src/ui/LoadingIndicator.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +class LoadingIndicator : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + +public: + LoadingIndicator(QWidget *parent = 0); + virtual ~LoadingIndicator(); + + void paintEvent(QPaintEvent *e); + + void start(); + void stop(); + + QColor color() { return color_; } + void setColor(QColor color) { color_ = color; } + + int interval() { return interval_; } + void setInterval(int interval) { interval_ = interval; } + +private slots: + void onTimeout(); + +private: + int interval_; + int angle_; + + QColor color_; + QTimer *timer_; +}; diff --git a/src/ui/Menu.h b/src/ui/Menu.h new file mode 100644 index 00000000..4c2a3c68 --- /dev/null +++ b/src/ui/Menu.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "Config.h" + +class Menu : public QMenu +{ +public: + Menu(QWidget *parent = nullptr) + : QMenu(parent) + { + QFont font; + font.setPixelSize(conf::fontSize); + + setFont(font); + setStyleSheet( + "QMenu { color: black; background-color: white; margin: 0px;}" + "QMenu::item {" + "color: black; padding: 7px 20px; border: 1px solid transparent;" + "margin: 2px 0px; }" + "QMenu::item:selected { color: black; background: rgba(180, 180, 180, 100); }"); + }; + +protected: + void leaveEvent(QEvent *e) + { + Q_UNUSED(e); + + hide(); + } +}; diff --git a/src/ui/OverlayModal.cc b/src/ui/OverlayModal.cc deleted file mode 100644 index 6aa16b07..00000000 --- a/src/ui/OverlayModal.cc +++ /dev/null @@ -1,60 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris - * - * This program 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. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include "OverlayModal.h" - -OverlayModal::OverlayModal(QWidget *parent, QWidget *content) - : OverlayWidget(parent) - , content_{content} - , color_{QColor(30, 30, 30, 170)} -{ - auto layout = new QVBoxLayout(); - layout->addWidget(content); - layout->setAlignment(Qt::AlignCenter); - - setLayout(layout); - - content->setFocus(); -} - -void -OverlayModal::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter painter(this); - painter.fillRect(rect(), color_); -} - -void -OverlayModal::mousePressEvent(QMouseEvent *e) -{ - if (isDismissible_ && content_ && !content_->geometry().contains(e->pos())) - hide(); -} - -void -OverlayModal::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Escape) { - event->accept(); - hide(); - } -} diff --git a/src/ui/OverlayModal.cpp b/src/ui/OverlayModal.cpp new file mode 100644 index 00000000..6aa16b07 --- /dev/null +++ b/src/ui/OverlayModal.cpp @@ -0,0 +1,60 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "OverlayModal.h" + +OverlayModal::OverlayModal(QWidget *parent, QWidget *content) + : OverlayWidget(parent) + , content_{content} + , color_{QColor(30, 30, 30, 170)} +{ + auto layout = new QVBoxLayout(); + layout->addWidget(content); + layout->setAlignment(Qt::AlignCenter); + + setLayout(layout); + + content->setFocus(); +} + +void +OverlayModal::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.fillRect(rect(), color_); +} + +void +OverlayModal::mousePressEvent(QMouseEvent *e) +{ + if (isDismissible_ && content_ && !content_->geometry().contains(e->pos())) + hide(); +} + +void +OverlayModal::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + event->accept(); + hide(); + } +} diff --git a/src/ui/OverlayModal.h b/src/ui/OverlayModal.h new file mode 100644 index 00000000..a761e3ed --- /dev/null +++ b/src/ui/OverlayModal.h @@ -0,0 +1,45 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include "OverlayWidget.h" + +class OverlayModal : public OverlayWidget +{ +public: + OverlayModal(QWidget *parent, QWidget *content); + + void setColor(QColor color) { color_ = color; } + void setDismissible(bool state) { isDismissible_ = state; } + +protected: + void paintEvent(QPaintEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private: + QWidget *content_; + QColor color_; + + //! Decides whether or not the modal can be removed by clicking into it. + bool isDismissible_ = true; +}; diff --git a/src/ui/OverlayWidget.cc b/src/ui/OverlayWidget.cc deleted file mode 100644 index ccac0116..00000000 --- a/src/ui/OverlayWidget.cc +++ /dev/null @@ -1,72 +0,0 @@ -#include "OverlayWidget.h" -#include - -OverlayWidget::OverlayWidget(QWidget *parent) - : QWidget(parent) -{ - if (parent) { - parent->installEventFilter(this); - setGeometry(overlayGeometry()); - raise(); - } -} - -bool -OverlayWidget::event(QEvent *event) -{ - if (!parent()) - return QWidget::event(event); - - switch (event->type()) { - case QEvent::ParentChange: { - parent()->installEventFilter(this); - setGeometry(overlayGeometry()); - break; - } - case QEvent::ParentAboutToChange: { - parent()->removeEventFilter(this); - break; - } - default: - break; - } - - return QWidget::event(event); -} - -bool -OverlayWidget::eventFilter(QObject *obj, QEvent *event) -{ - switch (event->type()) { - case QEvent::Move: - case QEvent::Resize: - setGeometry(overlayGeometry()); - break; - default: - break; - } - - return QWidget::eventFilter(obj, event); -} - -QRect -OverlayWidget::overlayGeometry() const -{ - QWidget *widget = parentWidget(); - - if (!widget) - return QRect(); - - return widget->rect(); -} - -void -OverlayWidget::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/ui/OverlayWidget.cpp b/src/ui/OverlayWidget.cpp new file mode 100644 index 00000000..ccac0116 --- /dev/null +++ b/src/ui/OverlayWidget.cpp @@ -0,0 +1,72 @@ +#include "OverlayWidget.h" +#include + +OverlayWidget::OverlayWidget(QWidget *parent) + : QWidget(parent) +{ + if (parent) { + parent->installEventFilter(this); + setGeometry(overlayGeometry()); + raise(); + } +} + +bool +OverlayWidget::event(QEvent *event) +{ + if (!parent()) + return QWidget::event(event); + + switch (event->type()) { + case QEvent::ParentChange: { + parent()->installEventFilter(this); + setGeometry(overlayGeometry()); + break; + } + case QEvent::ParentAboutToChange: { + parent()->removeEventFilter(this); + break; + } + default: + break; + } + + return QWidget::event(event); +} + +bool +OverlayWidget::eventFilter(QObject *obj, QEvent *event) +{ + switch (event->type()) { + case QEvent::Move: + case QEvent::Resize: + setGeometry(overlayGeometry()); + break; + default: + break; + } + + return QWidget::eventFilter(obj, event); +} + +QRect +OverlayWidget::overlayGeometry() const +{ + QWidget *widget = parentWidget(); + + if (!widget) + return QRect(); + + return widget->rect(); +} + +void +OverlayWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/src/ui/OverlayWidget.h b/src/ui/OverlayWidget.h new file mode 100644 index 00000000..6662479d --- /dev/null +++ b/src/ui/OverlayWidget.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +class OverlayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit OverlayWidget(QWidget *parent = nullptr); + +protected: + bool event(QEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; + + QRect overlayGeometry() const; + void paintEvent(QPaintEvent *event) override; +}; diff --git a/src/ui/Painter.h b/src/ui/Painter.h new file mode 100644 index 00000000..8de39651 --- /dev/null +++ b/src/ui/Painter.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include + +class Painter : public QPainter +{ +public: + explicit Painter(QPaintDevice *device) + : QPainter(device) + {} + + void drawTextLeft(int x, int y, const QString &text) + { + QFontMetrics m(fontMetrics()); + drawText(x, y + m.ascent(), text); + } + + void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) + { + QFontMetrics m(fontMetrics()); + if (textWidth < 0) + textWidth = m.width(text); + drawText((outerw - x - textWidth), y + m.ascent(), text); + } + + void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from) + { + drawPixmap(QPoint(x, y), pix, from); + } + + void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from) + { + return drawPixmapLeft(p.x(), p.y(), pix, from); + } + + void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from) + { + drawPixmap(QRect(x, y, w, h), pix, from); + } + + void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from) + { + return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from); + } + + void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix) + { + Q_UNUSED(outerw); + drawPixmap(QPoint(x, y), pix); + } + + void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix) + { + return drawPixmapLeft(p.x(), p.y(), outerw, pix); + } + + void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from) + { + drawPixmap( + QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from); + } + + void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) + { + return drawPixmapRight(p.x(), p.y(), outerw, pix, from); + } + void drawPixmapRight(int x, + int y, + int w, + int h, + int outerw, + const QPixmap &pix, + const QRect &from) + { + drawPixmap(QRect((outerw - x - w), y, w, h), pix, from); + } + + void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) + { + return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from); + } + + void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix) + { + drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix); + } + + void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix) + { + return drawPixmapRight(p.x(), p.y(), outerw, pix); + } + + void drawAvatar(const QPixmap &pix, int w, int h, int d) + { + QPainterPath pp; + pp.addEllipse((w - d) / 2, (h - d) / 2, d, d); + + QRect region((w - d) / 2, (h - d) / 2, d, d); + + setClipPath(pp); + drawPixmap(region, pix); + } + + void drawLetterAvatar(const QString &c, + const QColor &penColor, + const QColor &brushColor, + int w, + int h, + int d) + { + QRect region((w - d) / 2, (h - d) / 2, d, d); + + setPen(Qt::NoPen); + setBrush(brushColor); + + drawEllipse(region.center(), d / 2, d / 2); + + setBrush(Qt::NoBrush); + drawEllipse(region.center(), d / 2, d / 2); + + setPen(penColor); + drawText(region.translated(0, -1), Qt::AlignCenter, c); + } +}; + +class PainterHighQualityEnabler +{ +public: + PainterHighQualityEnabler(Painter &p) + : _painter(p) + { + static constexpr QPainter::RenderHint Hints[] = {QPainter::Antialiasing, + QPainter::SmoothPixmapTransform, + QPainter::TextAntialiasing, + QPainter::HighQualityAntialiasing}; + + auto hints = _painter.renderHints(); + for (const auto &hint : Hints) { + if (!(hints & hint)) + hints_ |= hint; + } + + if (hints_) + _painter.setRenderHints(hints_); + } + + ~PainterHighQualityEnabler() + { + if (hints_) + _painter.setRenderHints(hints_, false); + } + + PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete; + PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete; + +private: + Painter &_painter; + QPainter::RenderHints hints_ = 0; +}; diff --git a/src/ui/RaisedButton.cc b/src/ui/RaisedButton.cc deleted file mode 100644 index c519f84f..00000000 --- a/src/ui/RaisedButton.cc +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include - -#include "RaisedButton.h" - -void -RaisedButton::init() -{ - shadow_state_machine_ = new QStateMachine(this); - normal_state_ = new QState; - pressed_state_ = new QState; - effect_ = new QGraphicsDropShadowEffect; - - effect_->setBlurRadius(7); - effect_->setOffset(QPointF(0, 2)); - effect_->setColor(QColor(0, 0, 0, 75)); - - setBackgroundMode(Qt::OpaqueMode); - setMinimumHeight(42); - setGraphicsEffect(effect_); - setBaseOpacity(0.3); - - shadow_state_machine_->addState(normal_state_); - shadow_state_machine_->addState(pressed_state_); - - normal_state_->assignProperty(effect_, "offset", QPointF(0, 2)); - normal_state_->assignProperty(effect_, "blurRadius", 7); - - pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5)); - pressed_state_->assignProperty(effect_, "blurRadius", 29); - - QAbstractTransition *transition; - - transition = new QEventTransition(this, QEvent::MouseButtonPress); - transition->setTargetState(pressed_state_); - normal_state_->addTransition(transition); - - transition = new QEventTransition(this, QEvent::MouseButtonDblClick); - transition->setTargetState(pressed_state_); - normal_state_->addTransition(transition); - - transition = new QEventTransition(this, QEvent::MouseButtonRelease); - transition->setTargetState(normal_state_); - pressed_state_->addTransition(transition); - - QPropertyAnimation *animation; - - animation = new QPropertyAnimation(effect_, "offset", this); - animation->setDuration(100); - shadow_state_machine_->addDefaultAnimation(animation); - - animation = new QPropertyAnimation(effect_, "blurRadius", this); - animation->setDuration(100); - shadow_state_machine_->addDefaultAnimation(animation); - - shadow_state_machine_->setInitialState(normal_state_); - shadow_state_machine_->start(); -} - -RaisedButton::RaisedButton(QWidget *parent) - : FlatButton(parent) -{ - init(); -} - -RaisedButton::RaisedButton(const QString &text, QWidget *parent) - : FlatButton(parent) -{ - init(); - setText(text); -} - -RaisedButton::~RaisedButton() {} - -bool -RaisedButton::event(QEvent *event) -{ - if (QEvent::EnabledChange == event->type()) { - if (isEnabled()) { - shadow_state_machine_->start(); - effect_->setEnabled(true); - } else { - shadow_state_machine_->stop(); - effect_->setEnabled(false); - } - } - - return FlatButton::event(event); -} diff --git a/src/ui/RaisedButton.cpp b/src/ui/RaisedButton.cpp new file mode 100644 index 00000000..c519f84f --- /dev/null +++ b/src/ui/RaisedButton.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "RaisedButton.h" + +void +RaisedButton::init() +{ + shadow_state_machine_ = new QStateMachine(this); + normal_state_ = new QState; + pressed_state_ = new QState; + effect_ = new QGraphicsDropShadowEffect; + + effect_->setBlurRadius(7); + effect_->setOffset(QPointF(0, 2)); + effect_->setColor(QColor(0, 0, 0, 75)); + + setBackgroundMode(Qt::OpaqueMode); + setMinimumHeight(42); + setGraphicsEffect(effect_); + setBaseOpacity(0.3); + + shadow_state_machine_->addState(normal_state_); + shadow_state_machine_->addState(pressed_state_); + + normal_state_->assignProperty(effect_, "offset", QPointF(0, 2)); + normal_state_->assignProperty(effect_, "blurRadius", 7); + + pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5)); + pressed_state_->assignProperty(effect_, "blurRadius", 29); + + QAbstractTransition *transition; + + transition = new QEventTransition(this, QEvent::MouseButtonPress); + transition->setTargetState(pressed_state_); + normal_state_->addTransition(transition); + + transition = new QEventTransition(this, QEvent::MouseButtonDblClick); + transition->setTargetState(pressed_state_); + normal_state_->addTransition(transition); + + transition = new QEventTransition(this, QEvent::MouseButtonRelease); + transition->setTargetState(normal_state_); + pressed_state_->addTransition(transition); + + QPropertyAnimation *animation; + + animation = new QPropertyAnimation(effect_, "offset", this); + animation->setDuration(100); + shadow_state_machine_->addDefaultAnimation(animation); + + animation = new QPropertyAnimation(effect_, "blurRadius", this); + animation->setDuration(100); + shadow_state_machine_->addDefaultAnimation(animation); + + shadow_state_machine_->setInitialState(normal_state_); + shadow_state_machine_->start(); +} + +RaisedButton::RaisedButton(QWidget *parent) + : FlatButton(parent) +{ + init(); +} + +RaisedButton::RaisedButton(const QString &text, QWidget *parent) + : FlatButton(parent) +{ + init(); + setText(text); +} + +RaisedButton::~RaisedButton() {} + +bool +RaisedButton::event(QEvent *event) +{ + if (QEvent::EnabledChange == event->type()) { + if (isEnabled()) { + shadow_state_machine_->start(); + effect_->setEnabled(true); + } else { + shadow_state_machine_->stop(); + effect_->setEnabled(false); + } + } + + return FlatButton::event(event); +} diff --git a/src/ui/RaisedButton.h b/src/ui/RaisedButton.h new file mode 100644 index 00000000..edd5ee4a --- /dev/null +++ b/src/ui/RaisedButton.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include "FlatButton.h" + +class RaisedButton : public FlatButton +{ + Q_OBJECT + +public: + explicit RaisedButton(QWidget *parent = 0); + explicit RaisedButton(const QString &text, QWidget *parent = 0); + ~RaisedButton(); + +protected: + bool event(QEvent *event) override; + +private: + void init(); + + QStateMachine *shadow_state_machine_; + QState *normal_state_; + QState *pressed_state_; + QGraphicsDropShadowEffect *effect_; +}; diff --git a/src/ui/Ripple.cc b/src/ui/Ripple.cc deleted file mode 100644 index e22c4a62..00000000 --- a/src/ui/Ripple.cc +++ /dev/null @@ -1,107 +0,0 @@ -#include "Ripple.h" -#include "RippleOverlay.h" - -Ripple::Ripple(const QPoint ¢er, QObject *parent) - : QParallelAnimationGroup(parent) - , overlay_(0) - , radius_anim_(animate("radius")) - , opacity_anim_(animate("opacity")) - , radius_(0) - , opacity_(0) - , center_(center) -{ - init(); -} - -Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent) - : QParallelAnimationGroup(parent) - , overlay_(overlay) - , radius_anim_(animate("radius")) - , opacity_anim_(animate("opacity")) - , radius_(0) - , opacity_(0) - , center_(center) -{ - init(); -} - -void -Ripple::setRadius(qreal radius) -{ - Q_ASSERT(overlay_); - - if (radius_ == radius) - return; - - radius_ = radius; - overlay_->update(); -} - -void -Ripple::setOpacity(qreal opacity) -{ - Q_ASSERT(overlay_); - - if (opacity_ == opacity) - return; - - opacity_ = opacity; - overlay_->update(); -} - -void -Ripple::setColor(const QColor &color) -{ - if (brush_.color() == color) - return; - - brush_.setColor(color); - - if (overlay_) - overlay_->update(); -} - -void -Ripple::setBrush(const QBrush &brush) -{ - brush_ = brush; - - if (overlay_) - overlay_->update(); -} - -void -Ripple::destroy() -{ - Q_ASSERT(overlay_); - - overlay_->removeRipple(this); -} - -QPropertyAnimation * -Ripple::animate(const QByteArray &property, const QEasingCurve &easing, int duration) -{ - QPropertyAnimation *animation = new QPropertyAnimation; - animation->setTargetObject(this); - animation->setPropertyName(property); - animation->setEasingCurve(easing); - animation->setDuration(duration); - - addAnimation(animation); - - return animation; -} - -void -Ripple::init() -{ - setOpacityStartValue(0.5); - setOpacityEndValue(0); - setRadiusStartValue(0); - setRadiusEndValue(300); - - brush_.setColor(Qt::black); - brush_.setStyle(Qt::SolidPattern); - - connect(this, SIGNAL(finished()), this, SLOT(destroy())); -} diff --git a/src/ui/Ripple.cpp b/src/ui/Ripple.cpp new file mode 100644 index 00000000..e22c4a62 --- /dev/null +++ b/src/ui/Ripple.cpp @@ -0,0 +1,107 @@ +#include "Ripple.h" +#include "RippleOverlay.h" + +Ripple::Ripple(const QPoint ¢er, QObject *parent) + : QParallelAnimationGroup(parent) + , overlay_(0) + , radius_anim_(animate("radius")) + , opacity_anim_(animate("opacity")) + , radius_(0) + , opacity_(0) + , center_(center) +{ + init(); +} + +Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent) + : QParallelAnimationGroup(parent) + , overlay_(overlay) + , radius_anim_(animate("radius")) + , opacity_anim_(animate("opacity")) + , radius_(0) + , opacity_(0) + , center_(center) +{ + init(); +} + +void +Ripple::setRadius(qreal radius) +{ + Q_ASSERT(overlay_); + + if (radius_ == radius) + return; + + radius_ = radius; + overlay_->update(); +} + +void +Ripple::setOpacity(qreal opacity) +{ + Q_ASSERT(overlay_); + + if (opacity_ == opacity) + return; + + opacity_ = opacity; + overlay_->update(); +} + +void +Ripple::setColor(const QColor &color) +{ + if (brush_.color() == color) + return; + + brush_.setColor(color); + + if (overlay_) + overlay_->update(); +} + +void +Ripple::setBrush(const QBrush &brush) +{ + brush_ = brush; + + if (overlay_) + overlay_->update(); +} + +void +Ripple::destroy() +{ + Q_ASSERT(overlay_); + + overlay_->removeRipple(this); +} + +QPropertyAnimation * +Ripple::animate(const QByteArray &property, const QEasingCurve &easing, int duration) +{ + QPropertyAnimation *animation = new QPropertyAnimation; + animation->setTargetObject(this); + animation->setPropertyName(property); + animation->setEasingCurve(easing); + animation->setDuration(duration); + + addAnimation(animation); + + return animation; +} + +void +Ripple::init() +{ + setOpacityStartValue(0.5); + setOpacityEndValue(0); + setRadiusStartValue(0); + setRadiusEndValue(300); + + brush_.setColor(Qt::black); + brush_.setStyle(Qt::SolidPattern); + + connect(this, SIGNAL(finished()), this, SLOT(destroy())); +} diff --git a/src/ui/Ripple.h b/src/ui/Ripple.h new file mode 100644 index 00000000..9184f061 --- /dev/null +++ b/src/ui/Ripple.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include +#include + +class RippleOverlay; + +class Ripple : public QParallelAnimationGroup +{ + Q_OBJECT + + Q_PROPERTY(qreal radius WRITE setRadius READ radius) + Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity) + +public: + explicit Ripple(const QPoint ¢er, QObject *parent = 0); + Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = 0); + + inline void setOverlay(RippleOverlay *overlay); + + void setRadius(qreal radius); + void setOpacity(qreal opacity); + void setColor(const QColor &color); + void setBrush(const QBrush &brush); + + inline qreal radius() const; + inline qreal opacity() const; + inline QColor color() const; + inline QBrush brush() const; + inline QPoint center() const; + + inline QPropertyAnimation *radiusAnimation() const; + inline QPropertyAnimation *opacityAnimation() const; + + inline void setOpacityStartValue(qreal value); + inline void setOpacityEndValue(qreal value); + inline void setRadiusStartValue(qreal value); + inline void setRadiusEndValue(qreal value); + inline void setDuration(int msecs); + +protected slots: + void destroy(); + +private: + Q_DISABLE_COPY(Ripple) + + QPropertyAnimation *animate(const QByteArray &property, + const QEasingCurve &easing = QEasingCurve::OutQuad, + int duration = 800); + + void init(); + + RippleOverlay *overlay_; + + QPropertyAnimation *const radius_anim_; + QPropertyAnimation *const opacity_anim_; + + qreal radius_; + qreal opacity_; + + QPoint center_; + QBrush brush_; +}; + +inline void +Ripple::setOverlay(RippleOverlay *overlay) +{ + overlay_ = overlay; +} + +inline qreal +Ripple::radius() const +{ + return radius_; +} + +inline qreal +Ripple::opacity() const +{ + return opacity_; +} + +inline QColor +Ripple::color() const +{ + return brush_.color(); +} + +inline QBrush +Ripple::brush() const +{ + return brush_; +} + +inline QPoint +Ripple::center() const +{ + return center_; +} + +inline QPropertyAnimation * +Ripple::radiusAnimation() const +{ + return radius_anim_; +} + +inline QPropertyAnimation * +Ripple::opacityAnimation() const +{ + return opacity_anim_; +} + +inline void +Ripple::setOpacityStartValue(qreal value) +{ + opacity_anim_->setStartValue(value); +} + +inline void +Ripple::setOpacityEndValue(qreal value) +{ + opacity_anim_->setEndValue(value); +} + +inline void +Ripple::setRadiusStartValue(qreal value) +{ + radius_anim_->setStartValue(value); +} + +inline void +Ripple::setRadiusEndValue(qreal value) +{ + radius_anim_->setEndValue(value); +} + +inline void +Ripple::setDuration(int msecs) +{ + radius_anim_->setDuration(msecs); + opacity_anim_->setDuration(msecs); +} diff --git a/src/ui/RippleOverlay.cc b/src/ui/RippleOverlay.cc deleted file mode 100644 index 20e98c0f..00000000 --- a/src/ui/RippleOverlay.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include "Ripple.h" -#include "RippleOverlay.h" - -RippleOverlay::RippleOverlay(QWidget *parent) - : OverlayWidget(parent) - , use_clip_(false) -{ - setAttribute(Qt::WA_TransparentForMouseEvents); - setAttribute(Qt::WA_NoSystemBackground); -} - -void -RippleOverlay::addRipple(Ripple *ripple) -{ - ripple->setOverlay(this); - ripples_.push_back(ripple); - ripple->start(); -} - -void -RippleOverlay::addRipple(const QPoint &position, qreal radius) -{ - Ripple *ripple = new Ripple(position); - ripple->setRadiusEndValue(radius); - addRipple(ripple); -} - -void -RippleOverlay::removeRipple(Ripple *ripple) -{ - if (ripples_.removeOne(ripple)) - delete ripple; -} - -void -RippleOverlay::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(Qt::NoPen); - - if (use_clip_) - painter.setClipPath(clip_path_); - - for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it) - paintRipple(&painter, *it); -} - -void -RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple) -{ - const qreal radius = ripple->radius(); - const QPointF center = ripple->center(); - - painter->setOpacity(ripple->opacity()); - painter->setBrush(ripple->brush()); - painter->drawEllipse(center, radius, radius); -} diff --git a/src/ui/RippleOverlay.cpp b/src/ui/RippleOverlay.cpp new file mode 100644 index 00000000..20e98c0f --- /dev/null +++ b/src/ui/RippleOverlay.cpp @@ -0,0 +1,62 @@ +#include + +#include "Ripple.h" +#include "RippleOverlay.h" + +RippleOverlay::RippleOverlay(QWidget *parent) + : OverlayWidget(parent) + , use_clip_(false) +{ + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_NoSystemBackground); +} + +void +RippleOverlay::addRipple(Ripple *ripple) +{ + ripple->setOverlay(this); + ripples_.push_back(ripple); + ripple->start(); +} + +void +RippleOverlay::addRipple(const QPoint &position, qreal radius) +{ + Ripple *ripple = new Ripple(position); + ripple->setRadiusEndValue(radius); + addRipple(ripple); +} + +void +RippleOverlay::removeRipple(Ripple *ripple) +{ + if (ripples_.removeOne(ripple)) + delete ripple; +} + +void +RippleOverlay::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + + if (use_clip_) + painter.setClipPath(clip_path_); + + for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it) + paintRipple(&painter, *it); +} + +void +RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple) +{ + const qreal radius = ripple->radius(); + const QPointF center = ripple->center(); + + painter->setOpacity(ripple->opacity()); + painter->setBrush(ripple->brush()); + painter->drawEllipse(center, radius, radius); +} diff --git a/src/ui/RippleOverlay.h b/src/ui/RippleOverlay.h new file mode 100644 index 00000000..9ef91fbf --- /dev/null +++ b/src/ui/RippleOverlay.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "OverlayWidget.h" + +class Ripple; + +class RippleOverlay : public OverlayWidget +{ + Q_OBJECT + +public: + explicit RippleOverlay(QWidget *parent = 0); + + void addRipple(Ripple *ripple); + void addRipple(const QPoint &position, qreal radius = 300); + + void removeRipple(Ripple *ripple); + + inline void setClipping(bool enable); + inline bool hasClipping() const; + + inline void setClipPath(const QPainterPath &path); + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(RippleOverlay) + + void paintRipple(QPainter *painter, Ripple *ripple); + + QList ripples_; + QPainterPath clip_path_; + bool use_clip_; +}; + +inline void +RippleOverlay::setClipping(bool enable) +{ + use_clip_ = enable; + update(); +} + +inline bool +RippleOverlay::hasClipping() const +{ + return use_clip_; +} + +inline void +RippleOverlay::setClipPath(const QPainterPath &path) +{ + clip_path_ = path; + update(); +} diff --git a/src/ui/ScrollBar.cc b/src/ui/ScrollBar.cc deleted file mode 100644 index 37218a13..00000000 --- a/src/ui/ScrollBar.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris - * - * This program 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. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ScrollBar.h" - -ScrollBar::ScrollBar(QScrollArea *area, QWidget *parent) - : QScrollBar(parent) - , area_{area} -{} - -void -ScrollBar::paintEvent(QPaintEvent *) -{ - if (!width() && !height()) { - hide(); - return; - } - - QPainter p(this); - p.setRenderHint(QPainter::TextAntialiasing); - p.setRenderHint(QPainter::Antialiasing); - p.setRenderHint(QPainter::SmoothPixmapTransform); - - p.setPen(Qt::NoPen); - - p.setBrush(backgroundColor()); - QRect backgroundArea(Padding, 0, handleWidth_, height()); - p.drawRoundedRect(backgroundArea, roundRadius_, roundRadius_); - - int areaHeight = area_->height(); - int widgetHeight = area_->widget()->height(); - - double visiblePercentage = (double)areaHeight / (double)widgetHeight; - int handleHeight = std::max(visiblePercentage * areaHeight, (double)minHandleHeight_); - - if (maximum() == 0) { - return; - } - - int handle_y = (value() * (areaHeight - handleHeight - roundRadius_ / 2)) / maximum(); - - p.setBrush(handleColor()); - QRect handleArea(Padding, handle_y, handleWidth_, handleHeight); - p.drawRoundedRect(handleArea, roundRadius_, roundRadius_); -} diff --git a/src/ui/ScrollBar.cpp b/src/ui/ScrollBar.cpp new file mode 100644 index 00000000..37218a13 --- /dev/null +++ b/src/ui/ScrollBar.cpp @@ -0,0 +1,59 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScrollBar.h" + +ScrollBar::ScrollBar(QScrollArea *area, QWidget *parent) + : QScrollBar(parent) + , area_{area} +{} + +void +ScrollBar::paintEvent(QPaintEvent *) +{ + if (!width() && !height()) { + hide(); + return; + } + + QPainter p(this); + p.setRenderHint(QPainter::TextAntialiasing); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + + p.setPen(Qt::NoPen); + + p.setBrush(backgroundColor()); + QRect backgroundArea(Padding, 0, handleWidth_, height()); + p.drawRoundedRect(backgroundArea, roundRadius_, roundRadius_); + + int areaHeight = area_->height(); + int widgetHeight = area_->widget()->height(); + + double visiblePercentage = (double)areaHeight / (double)widgetHeight; + int handleHeight = std::max(visiblePercentage * areaHeight, (double)minHandleHeight_); + + if (maximum() == 0) { + return; + } + + int handle_y = (value() * (areaHeight - handleHeight - roundRadius_ / 2)) / maximum(); + + p.setBrush(handleColor()); + QRect handleArea(Padding, handle_y, handleWidth_, handleHeight); + p.drawRoundedRect(handleArea, roundRadius_, roundRadius_); +} diff --git a/src/ui/ScrollBar.h b/src/ui/ScrollBar.h new file mode 100644 index 00000000..2b5382aa --- /dev/null +++ b/src/ui/ScrollBar.h @@ -0,0 +1,54 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +class ScrollBar : public QScrollBar +{ + Q_OBJECT + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_PROPERTY(QColor handleColor READ handleColor WRITE setHandleColor) + +public: + ScrollBar(QScrollArea *area, QWidget *parent = nullptr); + + QColor backgroundColor() const { return bgColor_; } + void setBackgroundColor(QColor &color) { bgColor_ = color; } + + QColor handleColor() const { return handleColor_; } + void setHandleColor(QColor &color) { handleColor_ = color; } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + int roundRadius_ = 4; + int handleWidth_ = 7; + int minHandleHeight_ = 20; + + const int Padding = 4; + + QScrollArea *area_; + QRect handle_; + + QColor bgColor_ = QColor(33, 33, 33, 30); + QColor handleColor_ = QColor(0, 0, 0, 80); +}; diff --git a/src/ui/SnackBar.cc b/src/ui/SnackBar.cc deleted file mode 100644 index 43a4c85d..00000000 --- a/src/ui/SnackBar.cc +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include - -#include - -#include "SnackBar.h" - -constexpr int STARTING_OFFSET = 1; - -SnackBar::SnackBar(QWidget *parent) - : OverlayWidget(parent) -{ - bgOpacity_ = 0.9; - duration_ = 6000; - boxWidth_ = 400; - boxHeight_ = 40; - boxPadding_ = 10; - textColor_ = QColor("white"); - bgColor_ = QColor("#333"); - offset_ = STARTING_OFFSET; - position_ = SnackBarPosition::Top; - - QFont font("Open Sans"); - font.setPixelSize(14); - font.setWeight(50); - setFont(font); - - hideTimer_.setSingleShot(true); - - auto offset_anim = tweeny::from(1.0f).to(0.0f).during(100).via(tweeny::easing::cubicOut); - connect(&showTimer_, &QTimer::timeout, this, [this, offset_anim]() mutable { - if (offset_anim.progress() < 1.0f) { - offset_ = offset_anim.step(0.07f); - update(); - } else { - showTimer_.stop(); - hideTimer_.start(duration_); - offset_anim.seek(0.0f); - } - }); - - connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage())); - - hide(); -} - -void -SnackBar::start() -{ - if (messages_.empty()) - return; - - show(); - raise(); - - showTimer_.start(10); -} - -void -SnackBar::hideMessage() -{ - stopTimers(); - hide(); - - if (!messages_.empty()) - // Moving on to the next message. - messages_.pop_front(); - - // Reseting the starting position of the widget. - offset_ = STARTING_OFFSET; - - if (!messages_.empty()) - start(); -} - -void -SnackBar::stopTimers() -{ - showTimer_.stop(); - hideTimer_.stop(); -} - -void -SnackBar::showMessage(const QString &msg) -{ - messages_.push_back(msg); - - // There is already an active message. - if (isVisible()) - return; - - start(); -} - -void -SnackBar::mousePressEvent(QMouseEvent *) -{ - hideMessage(); -} - -void -SnackBar::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - if (messages_.empty()) - return; - - auto message_ = messages_.front(); - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(bgColor_); - p.setBrush(brush); - p.setOpacity(bgOpacity_); - - QRect r(0, 0, boxWidth_, boxHeight_); - - p.setPen(Qt::white); - QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); - - p.setPen(Qt::NoPen); - r = br.united(r).adjusted(-boxPadding_, -boxPadding_, boxPadding_, boxPadding_); - - const qreal s = 1 - offset_; - - if (position_ == SnackBarPosition::Bottom) - p.translate((width() - (r.width() - 2 * boxPadding_)) / 2, - height() - boxPadding_ - s * (r.height())); - else - p.translate((width() - (r.width() - 2 * boxPadding_)) / 2, - s * (r.height()) - 2 * boxPadding_); - - br.moveCenter(r.center()); - p.drawRoundedRect(r.adjusted(0, 0, 0, 3), 3, 3); - p.setPen(textColor_); - p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); -} diff --git a/src/ui/SnackBar.cpp b/src/ui/SnackBar.cpp new file mode 100644 index 00000000..43a4c85d --- /dev/null +++ b/src/ui/SnackBar.cpp @@ -0,0 +1,141 @@ +#include +#include + +#include + +#include "SnackBar.h" + +constexpr int STARTING_OFFSET = 1; + +SnackBar::SnackBar(QWidget *parent) + : OverlayWidget(parent) +{ + bgOpacity_ = 0.9; + duration_ = 6000; + boxWidth_ = 400; + boxHeight_ = 40; + boxPadding_ = 10; + textColor_ = QColor("white"); + bgColor_ = QColor("#333"); + offset_ = STARTING_OFFSET; + position_ = SnackBarPosition::Top; + + QFont font("Open Sans"); + font.setPixelSize(14); + font.setWeight(50); + setFont(font); + + hideTimer_.setSingleShot(true); + + auto offset_anim = tweeny::from(1.0f).to(0.0f).during(100).via(tweeny::easing::cubicOut); + connect(&showTimer_, &QTimer::timeout, this, [this, offset_anim]() mutable { + if (offset_anim.progress() < 1.0f) { + offset_ = offset_anim.step(0.07f); + update(); + } else { + showTimer_.stop(); + hideTimer_.start(duration_); + offset_anim.seek(0.0f); + } + }); + + connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage())); + + hide(); +} + +void +SnackBar::start() +{ + if (messages_.empty()) + return; + + show(); + raise(); + + showTimer_.start(10); +} + +void +SnackBar::hideMessage() +{ + stopTimers(); + hide(); + + if (!messages_.empty()) + // Moving on to the next message. + messages_.pop_front(); + + // Reseting the starting position of the widget. + offset_ = STARTING_OFFSET; + + if (!messages_.empty()) + start(); +} + +void +SnackBar::stopTimers() +{ + showTimer_.stop(); + hideTimer_.stop(); +} + +void +SnackBar::showMessage(const QString &msg) +{ + messages_.push_back(msg); + + // There is already an active message. + if (isVisible()) + return; + + start(); +} + +void +SnackBar::mousePressEvent(QMouseEvent *) +{ + hideMessage(); +} + +void +SnackBar::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + if (messages_.empty()) + return; + + auto message_ = messages_.front(); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(bgColor_); + p.setBrush(brush); + p.setOpacity(bgOpacity_); + + QRect r(0, 0, boxWidth_, boxHeight_); + + p.setPen(Qt::white); + QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); + + p.setPen(Qt::NoPen); + r = br.united(r).adjusted(-boxPadding_, -boxPadding_, boxPadding_, boxPadding_); + + const qreal s = 1 - offset_; + + if (position_ == SnackBarPosition::Bottom) + p.translate((width() - (r.width() - 2 * boxPadding_)) / 2, + height() - boxPadding_ - s * (r.height())); + else + p.translate((width() - (r.width() - 2 * boxPadding_)) / 2, + s * (r.height()) - 2 * boxPadding_); + + br.moveCenter(r.center()); + p.drawRoundedRect(r.adjusted(0, 0, 0, 3), 3, 3); + p.setPen(textColor_); + p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); +} diff --git a/src/ui/SnackBar.h b/src/ui/SnackBar.h new file mode 100644 index 00000000..eed59c87 --- /dev/null +++ b/src/ui/SnackBar.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include + +#include "OverlayWidget.h" + +enum class SnackBarPosition +{ + Bottom, + Top, +}; + +class SnackBar : public OverlayWidget +{ + Q_OBJECT + +public: + explicit SnackBar(QWidget *parent); + + inline void setBackgroundColor(const QColor &color); + inline void setTextColor(const QColor &color); + inline void setPosition(SnackBarPosition pos); + +public slots: + void showMessage(const QString &msg); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private slots: + void hideMessage(); + +private: + void stopTimers(); + void start(); + + QColor bgColor_; + QColor textColor_; + + qreal bgOpacity_; + qreal offset_; + + std::deque messages_; + + QTimer showTimer_; + QTimer hideTimer_; + + int duration_; + int boxWidth_; + int boxHeight_; + int boxPadding_; + + SnackBarPosition position_; +}; + +inline void +SnackBar::setPosition(SnackBarPosition pos) +{ + position_ = pos; + update(); +} + +inline void +SnackBar::setBackgroundColor(const QColor &color) +{ + bgColor_ = color; + update(); +} + +inline void +SnackBar::setTextColor(const QColor &color) +{ + textColor_ = color; + update(); +} diff --git a/src/ui/TextField.cc b/src/ui/TextField.cc deleted file mode 100644 index 0c936e69..00000000 --- a/src/ui/TextField.cc +++ /dev/null @@ -1,363 +0,0 @@ -#include "TextField.h" - -#include -#include -#include -#include -#include -#include - -TextField::TextField(QWidget *parent) - : QLineEdit(parent) -{ - // Get rid of the focus border on macOS. - setAttribute(Qt::WA_MacShowFocusRect, 0); - - state_machine_ = new TextFieldStateMachine(this); - label_ = 0; - label_font_size_ = 15; - show_label_ = false; - background_color_ = QColor("white"); - - setFrame(false); - setAttribute(Qt::WA_Hover); - setMouseTracking(true); - setTextMargins(0, 4, 0, 6); - - QFont font("Open Sans"); - font.setPixelSize(14); - setFont(font); - - state_machine_->start(); - QCoreApplication::processEvents(); -} - -void -TextField::setBackgroundColor(const QColor &color) -{ - background_color_ = color; -} - -QColor -TextField::backgroundColor() const -{ - return background_color_; -} - -void -TextField::setShowLabel(bool value) -{ - if (show_label_ == value) { - return; - } - - show_label_ = value; - - if (!label_ && value) { - label_ = new TextFieldLabel(this); - state_machine_->setLabel(label_); - } - - if (value) { - setContentsMargins(0, 23, 0, 0); - } else { - setContentsMargins(0, 0, 0, 0); - } -} - -bool -TextField::hasLabel() const -{ - return show_label_; -} - -void -TextField::setLabelFontSize(qreal size) -{ - label_font_size_ = size; - - if (label_) { - QFont font(label_->font()); - font.setPixelSize(size); - label_->setFont(font); - label_->update(); - } -} - -qreal -TextField::labelFontSize() const -{ - return label_font_size_; -} - -void -TextField::setLabel(const QString &label) -{ - label_text_ = label; - setShowLabel(true); - label_->update(); -} - -QString -TextField::label() const -{ - return label_text_; -} - -void -TextField::setTextColor(const QColor &color) -{ - text_color_ = color; - setStyleSheet(QString("QLineEdit { color: %1; }").arg(color.name())); -} - -QColor -TextField::textColor() const -{ - if (!text_color_.isValid()) { - return QColor("black"); - } - - return text_color_; -} - -void -TextField::setLabelColor(const QColor &color) -{ - label_color_ = color; - update(); -} - -QColor -TextField::labelColor() const -{ - if (!label_color_.isValid()) { - return QColor("#abb"); // TODO: Move this into Theme.h - } - - return label_color_; -} - -void -TextField::setInkColor(const QColor &color) -{ - ink_color_ = color; - update(); -} - -QColor -TextField::inkColor() const -{ - if (!ink_color_.isValid()) { - return QColor("black"); - } - - return ink_color_; -} - -void -TextField::setUnderlineColor(const QColor &color) -{ - underline_color_ = color; - update(); -} - -QColor -TextField::underlineColor() const -{ - if (!underline_color_.isValid()) { - return QColor("black"); - } - - return underline_color_; -} - -bool -TextField::event(QEvent *event) -{ - switch (event->type()) { - case QEvent::Resize: - case QEvent::Move: { - if (label_) - label_->setGeometry(rect()); - break; - } - default: - break; - } - - return QLineEdit::event(event); -} - -void -TextField::paintEvent(QPaintEvent *event) -{ - QLineEdit::paintEvent(event); - - QPainter painter(this); - - if (text().isEmpty()) { - painter.setOpacity(1 - state_machine_->progress()); - painter.fillRect(rect(), backgroundColor()); - } - - const int y = height() - 1; - const int wd = width() - 5; - - QPen pen; - pen.setWidth(1); - pen.setColor(underlineColor()); - painter.setPen(pen); - painter.setOpacity(1); - painter.drawLine(2, y, wd, y); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(inkColor()); - - const qreal progress = state_machine_->progress(); - - if (progress > 0) { - painter.setPen(Qt::NoPen); - painter.setBrush(brush); - const int w = (1 - progress) * static_cast(wd / 2); - painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2); - } -} - -TextFieldStateMachine::TextFieldStateMachine(TextField *parent) - : QStateMachine(parent) - , text_field_(parent) -{ - normal_state_ = new QState; - focused_state_ = new QState; - - label_ = 0; - offset_anim_ = 0; - color_anim_ = 0; - progress_ = 0.0; - - addState(normal_state_); - addState(focused_state_); - - setInitialState(normal_state_); - - QEventTransition *transition; - QPropertyAnimation *animation; - - transition = new QEventTransition(parent, QEvent::FocusIn); - transition->setTargetState(focused_state_); - normal_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "progress", this); - animation->setEasingCurve(QEasingCurve::InCubic); - animation->setDuration(310); - transition->addAnimation(animation); - - transition = new QEventTransition(parent, QEvent::FocusOut); - transition->setTargetState(normal_state_); - focused_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "progress", this); - animation->setEasingCurve(QEasingCurve::OutCubic); - animation->setDuration(310); - transition->addAnimation(animation); - - normal_state_->assignProperty(this, "progress", 0); - focused_state_->assignProperty(this, "progress", 1); - - setupProperties(); - - connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties())); -} - -void -TextFieldStateMachine::setLabel(TextFieldLabel *label) -{ - if (label_) { - delete label_; - } - - if (offset_anim_) { - removeDefaultAnimation(offset_anim_); - delete offset_anim_; - } - - if (color_anim_) { - removeDefaultAnimation(color_anim_); - delete color_anim_; - } - - label_ = label; - - if (label_) { - offset_anim_ = new QPropertyAnimation(label_, "offset", this); - offset_anim_->setDuration(210); - offset_anim_->setEasingCurve(QEasingCurve::OutCubic); - addDefaultAnimation(offset_anim_); - - color_anim_ = new QPropertyAnimation(label_, "color", this); - color_anim_->setDuration(210); - addDefaultAnimation(color_anim_); - } - - setupProperties(); -} - -void -TextFieldStateMachine::setupProperties() -{ - if (label_) { - const int m = text_field_->textMargins().top(); - - if (text_field_->text().isEmpty()) { - normal_state_->assignProperty(label_, "offset", QPointF(0, 26)); - } else { - normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m)); - } - - focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m)); - focused_state_->assignProperty(label_, "color", text_field_->inkColor()); - normal_state_->assignProperty(label_, "color", text_field_->labelColor()); - - if (0 != label_->offset().y() && !text_field_->text().isEmpty()) { - label_->setOffset(QPointF(0, 0 - m)); - } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 && - text_field_->text().isEmpty()) { - label_->setOffset(QPointF(0, 26)); - } - } - - text_field_->update(); -} - -TextFieldLabel::TextFieldLabel(TextField *parent) - : QWidget(parent) - , text_field_(parent) -{ - x_ = 0; - y_ = 26; - scale_ = 1; - color_ = parent->labelColor(); - - QFont font("Open Sans SemiBold"); - font.setPixelSize(parent->labelFontSize()); - font.setLetterSpacing(QFont::PercentageSpacing, 102); - setFont(font); -} - -void -TextFieldLabel::paintEvent(QPaintEvent *) -{ - if (!text_field_->hasLabel()) - return; - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - painter.scale(scale_, scale_); - painter.setPen(color_); - painter.setOpacity(1); - - QPointF pos(2 + x_, height() - 36 + y_); - painter.drawText(pos.x(), pos.y(), text_field_->label()); -} diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp new file mode 100644 index 00000000..0c936e69 --- /dev/null +++ b/src/ui/TextField.cpp @@ -0,0 +1,363 @@ +#include "TextField.h" + +#include +#include +#include +#include +#include +#include + +TextField::TextField(QWidget *parent) + : QLineEdit(parent) +{ + // Get rid of the focus border on macOS. + setAttribute(Qt::WA_MacShowFocusRect, 0); + + state_machine_ = new TextFieldStateMachine(this); + label_ = 0; + label_font_size_ = 15; + show_label_ = false; + background_color_ = QColor("white"); + + setFrame(false); + setAttribute(Qt::WA_Hover); + setMouseTracking(true); + setTextMargins(0, 4, 0, 6); + + QFont font("Open Sans"); + font.setPixelSize(14); + setFont(font); + + state_machine_->start(); + QCoreApplication::processEvents(); +} + +void +TextField::setBackgroundColor(const QColor &color) +{ + background_color_ = color; +} + +QColor +TextField::backgroundColor() const +{ + return background_color_; +} + +void +TextField::setShowLabel(bool value) +{ + if (show_label_ == value) { + return; + } + + show_label_ = value; + + if (!label_ && value) { + label_ = new TextFieldLabel(this); + state_machine_->setLabel(label_); + } + + if (value) { + setContentsMargins(0, 23, 0, 0); + } else { + setContentsMargins(0, 0, 0, 0); + } +} + +bool +TextField::hasLabel() const +{ + return show_label_; +} + +void +TextField::setLabelFontSize(qreal size) +{ + label_font_size_ = size; + + if (label_) { + QFont font(label_->font()); + font.setPixelSize(size); + label_->setFont(font); + label_->update(); + } +} + +qreal +TextField::labelFontSize() const +{ + return label_font_size_; +} + +void +TextField::setLabel(const QString &label) +{ + label_text_ = label; + setShowLabel(true); + label_->update(); +} + +QString +TextField::label() const +{ + return label_text_; +} + +void +TextField::setTextColor(const QColor &color) +{ + text_color_ = color; + setStyleSheet(QString("QLineEdit { color: %1; }").arg(color.name())); +} + +QColor +TextField::textColor() const +{ + if (!text_color_.isValid()) { + return QColor("black"); + } + + return text_color_; +} + +void +TextField::setLabelColor(const QColor &color) +{ + label_color_ = color; + update(); +} + +QColor +TextField::labelColor() const +{ + if (!label_color_.isValid()) { + return QColor("#abb"); // TODO: Move this into Theme.h + } + + return label_color_; +} + +void +TextField::setInkColor(const QColor &color) +{ + ink_color_ = color; + update(); +} + +QColor +TextField::inkColor() const +{ + if (!ink_color_.isValid()) { + return QColor("black"); + } + + return ink_color_; +} + +void +TextField::setUnderlineColor(const QColor &color) +{ + underline_color_ = color; + update(); +} + +QColor +TextField::underlineColor() const +{ + if (!underline_color_.isValid()) { + return QColor("black"); + } + + return underline_color_; +} + +bool +TextField::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Resize: + case QEvent::Move: { + if (label_) + label_->setGeometry(rect()); + break; + } + default: + break; + } + + return QLineEdit::event(event); +} + +void +TextField::paintEvent(QPaintEvent *event) +{ + QLineEdit::paintEvent(event); + + QPainter painter(this); + + if (text().isEmpty()) { + painter.setOpacity(1 - state_machine_->progress()); + painter.fillRect(rect(), backgroundColor()); + } + + const int y = height() - 1; + const int wd = width() - 5; + + QPen pen; + pen.setWidth(1); + pen.setColor(underlineColor()); + painter.setPen(pen); + painter.setOpacity(1); + painter.drawLine(2, y, wd, y); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(inkColor()); + + const qreal progress = state_machine_->progress(); + + if (progress > 0) { + painter.setPen(Qt::NoPen); + painter.setBrush(brush); + const int w = (1 - progress) * static_cast(wd / 2); + painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2); + } +} + +TextFieldStateMachine::TextFieldStateMachine(TextField *parent) + : QStateMachine(parent) + , text_field_(parent) +{ + normal_state_ = new QState; + focused_state_ = new QState; + + label_ = 0; + offset_anim_ = 0; + color_anim_ = 0; + progress_ = 0.0; + + addState(normal_state_); + addState(focused_state_); + + setInitialState(normal_state_); + + QEventTransition *transition; + QPropertyAnimation *animation; + + transition = new QEventTransition(parent, QEvent::FocusIn); + transition->setTargetState(focused_state_); + normal_state_->addTransition(transition); + + animation = new QPropertyAnimation(this, "progress", this); + animation->setEasingCurve(QEasingCurve::InCubic); + animation->setDuration(310); + transition->addAnimation(animation); + + transition = new QEventTransition(parent, QEvent::FocusOut); + transition->setTargetState(normal_state_); + focused_state_->addTransition(transition); + + animation = new QPropertyAnimation(this, "progress", this); + animation->setEasingCurve(QEasingCurve::OutCubic); + animation->setDuration(310); + transition->addAnimation(animation); + + normal_state_->assignProperty(this, "progress", 0); + focused_state_->assignProperty(this, "progress", 1); + + setupProperties(); + + connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties())); +} + +void +TextFieldStateMachine::setLabel(TextFieldLabel *label) +{ + if (label_) { + delete label_; + } + + if (offset_anim_) { + removeDefaultAnimation(offset_anim_); + delete offset_anim_; + } + + if (color_anim_) { + removeDefaultAnimation(color_anim_); + delete color_anim_; + } + + label_ = label; + + if (label_) { + offset_anim_ = new QPropertyAnimation(label_, "offset", this); + offset_anim_->setDuration(210); + offset_anim_->setEasingCurve(QEasingCurve::OutCubic); + addDefaultAnimation(offset_anim_); + + color_anim_ = new QPropertyAnimation(label_, "color", this); + color_anim_->setDuration(210); + addDefaultAnimation(color_anim_); + } + + setupProperties(); +} + +void +TextFieldStateMachine::setupProperties() +{ + if (label_) { + const int m = text_field_->textMargins().top(); + + if (text_field_->text().isEmpty()) { + normal_state_->assignProperty(label_, "offset", QPointF(0, 26)); + } else { + normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m)); + } + + focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m)); + focused_state_->assignProperty(label_, "color", text_field_->inkColor()); + normal_state_->assignProperty(label_, "color", text_field_->labelColor()); + + if (0 != label_->offset().y() && !text_field_->text().isEmpty()) { + label_->setOffset(QPointF(0, 0 - m)); + } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 && + text_field_->text().isEmpty()) { + label_->setOffset(QPointF(0, 26)); + } + } + + text_field_->update(); +} + +TextFieldLabel::TextFieldLabel(TextField *parent) + : QWidget(parent) + , text_field_(parent) +{ + x_ = 0; + y_ = 26; + scale_ = 1; + color_ = parent->labelColor(); + + QFont font("Open Sans SemiBold"); + font.setPixelSize(parent->labelFontSize()); + font.setLetterSpacing(QFont::PercentageSpacing, 102); + setFont(font); +} + +void +TextFieldLabel::paintEvent(QPaintEvent *) +{ + if (!text_field_->hasLabel()) + return; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.scale(scale_, scale_); + painter.setPen(color_); + painter.setOpacity(1); + + QPointF pos(2 + x_, height() - 36 + y_); + painter.drawText(pos.x(), pos.y(), text_field_->label()); +} diff --git a/src/ui/TextField.h b/src/ui/TextField.h new file mode 100644 index 00000000..1675a2e0 --- /dev/null +++ b/src/ui/TextField.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class TextField; +class TextFieldLabel; +class TextFieldStateMachine; + +class TextField : public QLineEdit +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor) + Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor) + Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor) + Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) + +public: + explicit TextField(QWidget *parent = 0); + + void setInkColor(const QColor &color); + void setBackgroundColor(const QColor &color); + void setLabel(const QString &label); + void setLabelColor(const QColor &color); + void setLabelFontSize(qreal size); + void setShowLabel(bool value); + void setTextColor(const QColor &color); + void setUnderlineColor(const QColor &color); + + QColor inkColor() const; + QColor labelColor() const; + QColor textColor() const; + QColor underlineColor() const; + QColor backgroundColor() const; + QString label() const; + bool hasLabel() const; + qreal labelFontSize() const; + +protected: + bool event(QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + + QColor ink_color_; + QColor background_color_; + QColor label_color_; + QColor text_color_; + QColor underline_color_; + QString label_text_; + TextFieldLabel *label_; + TextFieldStateMachine *state_machine_; + bool show_label_; + qreal label_font_size_; +}; + +class TextFieldLabel : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(qreal scale WRITE setScale READ scale) + Q_PROPERTY(QPointF offset WRITE setOffset READ offset) + Q_PROPERTY(QColor color WRITE setColor READ color) + +public: + TextFieldLabel(TextField *parent); + + inline void setColor(const QColor &color); + inline void setOffset(const QPointF &pos); + inline void setScale(qreal scale); + + inline QColor color() const; + inline QPointF offset() const; + inline qreal scale() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + TextField *const text_field_; + + QColor color_; + qreal scale_; + qreal x_; + qreal y_; +}; + +inline void +TextFieldLabel::setColor(const QColor &color) +{ + color_ = color; + update(); +} + +inline void +TextFieldLabel::setOffset(const QPointF &pos) +{ + x_ = pos.x(); + y_ = pos.y(); + update(); +} + +inline void +TextFieldLabel::setScale(qreal scale) +{ + scale_ = scale; + update(); +} + +inline QPointF +TextFieldLabel::offset() const +{ + return QPointF(x_, y_); +} +inline qreal +TextFieldLabel::scale() const +{ + return scale_; +} +inline QColor +TextFieldLabel::color() const +{ + return color_; +} + +class TextFieldStateMachine : public QStateMachine +{ + Q_OBJECT + + Q_PROPERTY(qreal progress WRITE setProgress READ progress) + +public: + TextFieldStateMachine(TextField *parent); + + inline void setProgress(qreal progress); + void setLabel(TextFieldLabel *label); + + inline qreal progress() const; + +public slots: + void setupProperties(); + +private: + QPropertyAnimation *color_anim_; + QPropertyAnimation *offset_anim_; + + QState *focused_state_; + QState *normal_state_; + + TextField *text_field_; + TextFieldLabel *label_; + + qreal progress_; +}; + +inline void +TextFieldStateMachine::setProgress(qreal progress) +{ + progress_ = progress; + text_field_->update(); +} + +inline qreal +TextFieldStateMachine::progress() const +{ + return progress_; +} diff --git a/src/ui/Theme.cc b/src/ui/Theme.cc deleted file mode 100644 index 7209864a..00000000 --- a/src/ui/Theme.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include "Theme.h" - -Theme::Theme(QObject *parent) - : QObject(parent) -{ - setColor("Black", ui::Color::Black); - - setColor("BrightWhite", ui::Color::BrightWhite); - setColor("FadedWhite", ui::Color::FadedWhite); - setColor("MediumWhite", ui::Color::MediumWhite); - - setColor("BrightGreen", ui::Color::BrightGreen); - setColor("DarkGreen", ui::Color::DarkGreen); - setColor("LightGreen", ui::Color::LightGreen); - - setColor("Gray", ui::Color::Gray); - setColor("Red", ui::Color::Red); - setColor("Blue", ui::Color::Blue); - - setColor("Transparent", ui::Color::Transparent); -} - -QColor -Theme::rgba(int r, int g, int b, qreal a) const -{ - QColor color(r, g, b); - color.setAlphaF(a); - - return color; -} - -QColor -Theme::getColor(const QString &key) const -{ - if (!colors_.contains(key)) { - qWarning() << "Color with key" << key << "could not be found"; - return QColor(); - } - - return colors_.value(key); -} - -void -Theme::setColor(const QString &key, const QColor &color) -{ - colors_.insert(key, color); -} - -void -Theme::setColor(const QString &key, ui::Color color) -{ - static const QColor palette[] = { - QColor("#171919"), - - QColor("#EBEBEB"), - QColor("#C9C9C9"), - QColor("#929292"), - - QColor("#1C3133"), - QColor("#577275"), - QColor("#46A451"), - - QColor("#5D6565"), - QColor("#E22826"), - QColor("#81B3A9"), - - rgba(0, 0, 0, 0), - }; - - colors_.insert(key, palette[static_cast(color)]); -} diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp new file mode 100644 index 00000000..7209864a --- /dev/null +++ b/src/ui/Theme.cpp @@ -0,0 +1,73 @@ +#include + +#include "Theme.h" + +Theme::Theme(QObject *parent) + : QObject(parent) +{ + setColor("Black", ui::Color::Black); + + setColor("BrightWhite", ui::Color::BrightWhite); + setColor("FadedWhite", ui::Color::FadedWhite); + setColor("MediumWhite", ui::Color::MediumWhite); + + setColor("BrightGreen", ui::Color::BrightGreen); + setColor("DarkGreen", ui::Color::DarkGreen); + setColor("LightGreen", ui::Color::LightGreen); + + setColor("Gray", ui::Color::Gray); + setColor("Red", ui::Color::Red); + setColor("Blue", ui::Color::Blue); + + setColor("Transparent", ui::Color::Transparent); +} + +QColor +Theme::rgba(int r, int g, int b, qreal a) const +{ + QColor color(r, g, b); + color.setAlphaF(a); + + return color; +} + +QColor +Theme::getColor(const QString &key) const +{ + if (!colors_.contains(key)) { + qWarning() << "Color with key" << key << "could not be found"; + return QColor(); + } + + return colors_.value(key); +} + +void +Theme::setColor(const QString &key, const QColor &color) +{ + colors_.insert(key, color); +} + +void +Theme::setColor(const QString &key, ui::Color color) +{ + static const QColor palette[] = { + QColor("#171919"), + + QColor("#EBEBEB"), + QColor("#C9C9C9"), + QColor("#929292"), + + QColor("#1C3133"), + QColor("#577275"), + QColor("#46A451"), + + QColor("#5D6565"), + QColor("#E22826"), + QColor("#81B3A9"), + + rgba(0, 0, 0, 0), + }; + + colors_.insert(key, palette[static_cast(color)]); +} diff --git a/src/ui/Theme.h b/src/ui/Theme.h new file mode 100644 index 00000000..7a0bdcb7 --- /dev/null +++ b/src/ui/Theme.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +namespace ui { +enum class AvatarType +{ + Icon, + Image, + Letter +}; + +namespace sidebar { +static const int SmallSize = 60; +static const int NormalSize = 260; +static const int CommunitiesSidebarSize = 48; +} +// Default font size. +const int FontSize = 16; + +// Default avatar size. Width and height. +const int AvatarSize = 40; + +enum class ButtonPreset +{ + FlatPreset, + CheckablePreset +}; + +enum class RippleStyle +{ + CenteredRipple, + PositionedRipple, + NoRipple +}; + +enum class OverlayStyle +{ + NoOverlay, + TintedOverlay, + GrayOverlay +}; + +enum class Role +{ + Default, + Primary, + Secondary +}; + +enum class ButtonIconPlacement +{ + LeftIcon, + RightIcon +}; + +enum class ProgressType +{ + DeterminateProgress, + IndeterminateProgress +}; + +enum class Color +{ + Black, + BrightWhite, + FadedWhite, + MediumWhite, + DarkGreen, + LightGreen, + BrightGreen, + Gray, + Red, + Blue, + Transparent +}; + +} // namespace ui + +class Theme : public QObject +{ + Q_OBJECT +public: + explicit Theme(QObject *parent = 0); + + QColor getColor(const QString &key) const; + + void setColor(const QString &key, const QColor &color); + void setColor(const QString &key, ui::Color color); + +private: + QColor rgba(int r, int g, int b, qreal a) const; + + QHash colors_; +}; diff --git a/src/ui/ThemeManager.cc b/src/ui/ThemeManager.cc deleted file mode 100644 index 7baed1f3..00000000 --- a/src/ui/ThemeManager.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include "ThemeManager.h" - -ThemeManager::ThemeManager() { setTheme(new Theme); } - -void -ThemeManager::setTheme(Theme *theme) -{ - theme_ = theme; - theme_->setParent(this); -} - -QColor -ThemeManager::themeColor(const QString &key) const -{ - Q_ASSERT(theme_); - return theme_->getColor(key); -} diff --git a/src/ui/ThemeManager.cpp b/src/ui/ThemeManager.cpp new file mode 100644 index 00000000..7baed1f3 --- /dev/null +++ b/src/ui/ThemeManager.cpp @@ -0,0 +1,19 @@ +#include + +#include "ThemeManager.h" + +ThemeManager::ThemeManager() { setTheme(new Theme); } + +void +ThemeManager::setTheme(Theme *theme) +{ + theme_ = theme; + theme_->setParent(this); +} + +QColor +ThemeManager::themeColor(const QString &key) const +{ + Q_ASSERT(theme_); + return theme_->getColor(key); +} diff --git a/src/ui/ThemeManager.h b/src/ui/ThemeManager.h new file mode 100644 index 00000000..d35ff754 --- /dev/null +++ b/src/ui/ThemeManager.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "Theme.h" + +class ThemeManager : public QCommonStyle +{ + Q_OBJECT + +public: + inline static ThemeManager &instance(); + + void setTheme(Theme *theme); + QColor themeColor(const QString &key) const; + +private: + ThemeManager(); + + ThemeManager(ThemeManager const &); + void operator=(ThemeManager const &); + + Theme *theme_; +}; + +inline ThemeManager & +ThemeManager::instance() +{ + static ThemeManager instance; + return instance; +} diff --git a/src/ui/ToggleButton.cc b/src/ui/ToggleButton.cc deleted file mode 100644 index 755f528f..00000000 --- a/src/ui/ToggleButton.cc +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include - -#include "ToggleButton.h" - -void -Toggle::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); -} - -Toggle::Toggle(QWidget *parent) - : QAbstractButton{parent} -{ - init(); - - connect(this, &QAbstractButton::toggled, this, &Toggle::setState); -} - -void -Toggle::setState(bool isEnabled) -{ - thumb_->setShift(isEnabled ? Position::Right : Position::Left); - setupProperties(); -} - -void -Toggle::init() -{ - track_ = new ToggleTrack(this); - thumb_ = new ToggleThumb(this); - - setCursor(QCursor(Qt::PointingHandCursor)); - setCheckable(true); - setChecked(false); - - setState(false); - setupProperties(); - - QCoreApplication::processEvents(); -} - -void -Toggle::setupProperties() -{ - if (isEnabled()) { - Position position = thumb_->shift(); - - thumb_->setThumbColor(trackColor()); - - if (position == Position::Left) - track_->setTrackColor(activeColor()); - else if (position == Position::Right) - track_->setTrackColor(inactiveColor()); - } - - update(); -} - -void -Toggle::setDisabledColor(const QColor &color) -{ - disabledColor_ = color; - setupProperties(); -} - -void -Toggle::setActiveColor(const QColor &color) -{ - activeColor_ = color; - setupProperties(); -} - -void -Toggle::setInactiveColor(const QColor &color) -{ - inactiveColor_ = color; - setupProperties(); -} - -void -Toggle::setTrackColor(const QColor &color) -{ - trackColor_ = color; - setupProperties(); -} - -ToggleThumb::ToggleThumb(Toggle *parent) - : QWidget{parent} - , toggle_{parent} - , position_{Position::Right} - , offset_{0} -{ - parent->installEventFilter(this); -} - -void -ToggleThumb::setShift(Position position) -{ - if (position_ != position) { - position_ = position; - updateOffset(); - } -} - -bool -ToggleThumb::eventFilter(QObject *obj, QEvent *event) -{ - const QEvent::Type type = event->type(); - - if (QEvent::Resize == type || QEvent::Move == type) { - setGeometry(toggle_->rect().adjusted(8, 8, -8, -8)); - updateOffset(); - } - - return QWidget::eventFilter(obj, event); -} - -void -ToggleThumb::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white); - - painter.setBrush(brush); - painter.setPen(Qt::NoPen); - - int s; - QRectF r; - - s = height() - 10; - r = QRectF(5 + offset_, 5, s, s); - - painter.drawEllipse(r); - - if (!toggle_->isEnabled()) { - brush.setColor(toggle_->disabledColor()); - painter.setBrush(brush); - painter.drawEllipse(r); - } -} - -void -ToggleThumb::updateOffset() -{ - const QSize s(size()); - offset_ = position_ == Position::Left ? static_cast(s.width() - s.height()) : 0; - update(); -} - -ToggleTrack::ToggleTrack(Toggle *parent) - : QWidget{parent} - , toggle_{parent} -{ - Q_ASSERT(parent); - - parent->installEventFilter(this); -} - -void -ToggleTrack::setTrackColor(const QColor &color) -{ - trackColor_ = color; - update(); -} - -bool -ToggleTrack::eventFilter(QObject *obj, QEvent *event) -{ - const QEvent::Type type = event->type(); - - if (QEvent::Resize == type || QEvent::Move == type) { - setGeometry(toggle_->rect()); - } - - return QWidget::eventFilter(obj, event); -} - -void -ToggleTrack::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - QBrush brush; - if (toggle_->isEnabled()) { - brush.setColor(trackColor_); - painter.setOpacity(0.8); - } else { - brush.setColor(toggle_->disabledColor()); - painter.setOpacity(0.6); - } - - brush.setStyle(Qt::SolidPattern); - painter.setBrush(brush); - painter.setPen(Qt::NoPen); - - const int h = height() / 2; - const QRect r(0, h / 2, width(), h); - painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4); -} diff --git a/src/ui/ToggleButton.cpp b/src/ui/ToggleButton.cpp new file mode 100644 index 00000000..755f528f --- /dev/null +++ b/src/ui/ToggleButton.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include + +#include "ToggleButton.h" + +void +Toggle::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); +} + +Toggle::Toggle(QWidget *parent) + : QAbstractButton{parent} +{ + init(); + + connect(this, &QAbstractButton::toggled, this, &Toggle::setState); +} + +void +Toggle::setState(bool isEnabled) +{ + thumb_->setShift(isEnabled ? Position::Right : Position::Left); + setupProperties(); +} + +void +Toggle::init() +{ + track_ = new ToggleTrack(this); + thumb_ = new ToggleThumb(this); + + setCursor(QCursor(Qt::PointingHandCursor)); + setCheckable(true); + setChecked(false); + + setState(false); + setupProperties(); + + QCoreApplication::processEvents(); +} + +void +Toggle::setupProperties() +{ + if (isEnabled()) { + Position position = thumb_->shift(); + + thumb_->setThumbColor(trackColor()); + + if (position == Position::Left) + track_->setTrackColor(activeColor()); + else if (position == Position::Right) + track_->setTrackColor(inactiveColor()); + } + + update(); +} + +void +Toggle::setDisabledColor(const QColor &color) +{ + disabledColor_ = color; + setupProperties(); +} + +void +Toggle::setActiveColor(const QColor &color) +{ + activeColor_ = color; + setupProperties(); +} + +void +Toggle::setInactiveColor(const QColor &color) +{ + inactiveColor_ = color; + setupProperties(); +} + +void +Toggle::setTrackColor(const QColor &color) +{ + trackColor_ = color; + setupProperties(); +} + +ToggleThumb::ToggleThumb(Toggle *parent) + : QWidget{parent} + , toggle_{parent} + , position_{Position::Right} + , offset_{0} +{ + parent->installEventFilter(this); +} + +void +ToggleThumb::setShift(Position position) +{ + if (position_ != position) { + position_ = position; + updateOffset(); + } +} + +bool +ToggleThumb::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Resize == type || QEvent::Move == type) { + setGeometry(toggle_->rect().adjusted(8, 8, -8, -8)); + updateOffset(); + } + + return QWidget::eventFilter(obj, event); +} + +void +ToggleThumb::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white); + + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + int s; + QRectF r; + + s = height() - 10; + r = QRectF(5 + offset_, 5, s, s); + + painter.drawEllipse(r); + + if (!toggle_->isEnabled()) { + brush.setColor(toggle_->disabledColor()); + painter.setBrush(brush); + painter.drawEllipse(r); + } +} + +void +ToggleThumb::updateOffset() +{ + const QSize s(size()); + offset_ = position_ == Position::Left ? static_cast(s.width() - s.height()) : 0; + update(); +} + +ToggleTrack::ToggleTrack(Toggle *parent) + : QWidget{parent} + , toggle_{parent} +{ + Q_ASSERT(parent); + + parent->installEventFilter(this); +} + +void +ToggleTrack::setTrackColor(const QColor &color) +{ + trackColor_ = color; + update(); +} + +bool +ToggleTrack::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Resize == type || QEvent::Move == type) { + setGeometry(toggle_->rect()); + } + + return QWidget::eventFilter(obj, event); +} + +void +ToggleTrack::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + if (toggle_->isEnabled()) { + brush.setColor(trackColor_); + painter.setOpacity(0.8); + } else { + brush.setColor(toggle_->disabledColor()); + painter.setOpacity(0.6); + } + + brush.setStyle(Qt::SolidPattern); + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + const int h = height() / 2; + const QRect r(0, h / 2, width(), h); + painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4); +} diff --git a/src/ui/ToggleButton.h b/src/ui/ToggleButton.h new file mode 100644 index 00000000..14c3450b --- /dev/null +++ b/src/ui/ToggleButton.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +class ToggleTrack; +class ToggleThumb; + +enum class Position +{ + Left, + Right +}; + +class Toggle : public QAbstractButton +{ + Q_OBJECT + + Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor) + Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor) + Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor) + Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor) + +public: + Toggle(QWidget *parent = nullptr); + + void setState(bool isEnabled); + + void setActiveColor(const QColor &color); + void setDisabledColor(const QColor &color); + void setInactiveColor(const QColor &color); + void setTrackColor(const QColor &color); + + QColor activeColor() const { return activeColor_; }; + QColor disabledColor() const { return disabledColor_; }; + QColor inactiveColor() const { return inactiveColor_; }; + QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); }; + + QSize sizeHint() const override { return QSize(64, 48); }; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + void setupProperties(); + + ToggleTrack *track_; + ToggleThumb *thumb_; + + QColor disabledColor_; + QColor activeColor_; + QColor inactiveColor_; + QColor trackColor_; +}; + +class ToggleThumb : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor) + +public: + ToggleThumb(Toggle *parent); + + Position shift() const { return position_; }; + qreal offset() const { return offset_; }; + QColor thumbColor() const { return thumbColor_; }; + + void setShift(Position position); + void setThumbColor(const QColor &color) + { + thumbColor_ = color; + update(); + }; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + void updateOffset(); + + Toggle *const toggle_; + QColor thumbColor_; + + Position position_; + qreal offset_; +}; + +class ToggleTrack : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor) + +public: + ToggleTrack(Toggle *parent); + + void setTrackColor(const QColor &color); + QColor trackColor() const { return trackColor_; }; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + Toggle *const toggle_; + QColor trackColor_; +}; -- cgit 1.5.1