From 8d25f4036a39d0e59fbb1ff64e9ac7611f7b5b78 Mon Sep 17 00:00:00 2001 From: laserpants Date: Sat, 18 Jun 2016 11:50:33 +0300 Subject: [PATCH] implement Snackbar --- README.md | 2 +- components/badge.cpp | 4 + components/fab.cpp | 4 + components/snackbar.cpp | 237 ++++++++++++++++++++++++++++++- components/snackbar.h | 36 ++++- components/snackbar_internal.cpp | 89 ++++++++++++ components/snackbar_internal.h | 40 ++++++ components/snackbar_p.h | 25 ++++ mainwindow.cpp | 39 ++++- mainwindow.h | 4 + qt-material-widgets.pro | 6 +- 11 files changed, 479 insertions(+), 7 deletions(-) create mode 100644 components/snackbar_internal.cpp create mode 100644 components/snackbar_internal.h diff --git a/README.md b/README.md index 3e59608..a601e07 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - [x] Radio Button - [x] Raised Button - [x] Slider +- [x] Snackbar - [x] Tabs - [x] Text Field - [x] Toggle @@ -25,7 +26,6 @@ - [ ] Progress - [ ] Select Field - [ ] Scroll Bar -- [ ] Snackbar #### Not started diff --git a/components/badge.cpp b/components/badge.cpp index dc11504..a88c183 100644 --- a/components/badge.cpp +++ b/components/badge.cpp @@ -31,6 +31,10 @@ void BadgePrivate::init() q->setFont(font); q->setText("+1"); + + if (q->parentWidget()) { + q->parentWidget()->installEventFilter(q); + } } Badge::Badge(QWidget *parent) diff --git a/components/fab.cpp b/components/fab.cpp index 32386c0..438540b 100644 --- a/components/fab.cpp +++ b/components/fab.cpp @@ -30,6 +30,10 @@ void FloatingActionButtonPrivate::init() path.addEllipse(0, 0, 56, 56); ripple->setClipPath(path); ripple->setClipping(true); + + if (q->parentWidget()) { + q->parentWidget()->installEventFilter(q); + } } QRect FloatingActionButtonPrivate::fabGeometry() const diff --git a/components/snackbar.cpp b/components/snackbar.cpp index 326de83..c7636c9 100644 --- a/components/snackbar.cpp +++ b/components/snackbar.cpp @@ -1,10 +1,245 @@ #include "snackbar.h" +#include +#include +#include +#include "snackbar_p.h" +#include "snackbar_internal.h" + +SnackbarPrivate::SnackbarPrivate(Snackbar *q) + : q_ptr(q), + duration(3000), + boxWidth(300) +{ +} + +SnackbarPrivate::~SnackbarPrivate() +{ +} + +void SnackbarPrivate::init() +{ + Q_Q(Snackbar); + + machine = new SnackbarStateMachine(q); + + q->setAttribute(Qt::WA_TransparentForMouseEvents); + + QFont font(q->font()); + font.setPointSizeF(11); + q->setFont(font); + + backgroundColor = QColor(0, 0, 0, 220); + textColor = Qt::white; + + if (q->parentWidget()) { + q->parentWidget()->installEventFilter(q); + } + + machine->start(); +} Snackbar::Snackbar(QWidget *parent) - : QWidget(parent) + : QWidget(parent), + d_ptr(new SnackbarPrivate(this)) { + d_func()->init(); } Snackbar::~Snackbar() { } + +void Snackbar::setAutoHideDuration(int duration) +{ + Q_D(Snackbar); + + d->duration = duration; +} + +int Snackbar::autoHideDuration() const +{ + Q_D(const Snackbar); + + return d->duration; +} + +void Snackbar::setBackgroundColor(const QColor &color) +{ + Q_D(Snackbar); + + d->backgroundColor = color; +} + +QColor Snackbar::backgroundColor() const +{ + Q_D(const Snackbar); + + return d->backgroundColor; +} + +void Snackbar::setTextColor(const QColor &color) +{ + Q_D(Snackbar); + + d->textColor = color; +} + +QColor Snackbar::textColor() const +{ + Q_D(const Snackbar); + + return d->textColor; +} + +void Snackbar::setFontSize(qreal size) +{ + Q_D(Snackbar); + + QFont f(font()); + f.setPointSizeF(size); + setFont(f); + + update(); +} + +qreal Snackbar::fontSize() const +{ + Q_D(const Snackbar); + + return font().pointSizeF(); +} + +void Snackbar::setBoxWidth(int width) +{ + Q_D(Snackbar); + + d->boxWidth = width; + update(); +} + +int Snackbar::boxWidth() const +{ + Q_D(const Snackbar); + + return d->boxWidth; +} + +void Snackbar::addMessage(const QString &message, bool instant) +{ + Q_D(Snackbar); + + if (instant && !d->messages.isEmpty()) { + d->messages.insert(1, message); + } else { + d->messages.push_back(message); + } + + if (instant) { + emit d->machine->hideSnackbar(); + } else { + emit d->machine->showSnackbar(); + } +} + +bool Snackbar::event(QEvent *event) +{ + switch (event->type()) + { + case QEvent::ParentChange: + { + if (!parent()) + break; + + parent()->installEventFilter(this); + + QWidget *widget; + if ((widget = parentWidget())) { + setGeometry(widget->rect()); + } + break; + } + case QEvent::ParentAboutToChange: + { + if (!parent()) + break; + + parent()->removeEventFilter(this); + break; + } + default: + break; + } + return QWidget::event(event); +} + +bool Snackbar::eventFilter(QObject *obj, QEvent *event) +{ + QEvent::Type type = event->type(); + + if (QEvent::Move == type || QEvent::Resize == type) + { + QWidget *widget; + if ((widget = parentWidget())) { + setGeometry(widget->rect()); + } + } + return QWidget::eventFilter(obj, event); +} + +void Snackbar::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + Q_D(Snackbar); + + if (d->messages.isEmpty()) { + return; + } + + QString message = d->messages.first(); + + QPainter painter(this); + + QRectF r = painter.boundingRect(QRect((width()-d->boxWidth)/2, 0, d->boxWidth, 40), + Qt::AlignCenter | Qt::TextWordWrap, + message); + + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(d->backgroundColor); + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + QRectF s((width()-d->boxWidth)/2, 0, d->boxWidth, 40); + + s = r.united(s); + + const int yOffs = height()-s.height()-s.top() + + static_cast(s.height()+20)*d->machine->offset(); + + s.translate(0, yOffs); + r.translate(0, yOffs-5); + + painter.drawRoundedRect(s.adjusted(-10, -10, 10, 10), 3, 3); + painter.setPen(d->textColor); + painter.drawText(r, Qt::AlignCenter | Qt::TextWordWrap, message, &r); +} + +void Snackbar::dequeue() +{ + Q_D(Snackbar); + + if (d->messages.isEmpty()) { + return; + } + + d->messages.removeFirst(); + + if (!d->messages.isEmpty()) { + emit d->machine->showNextSnackbar(); + } else { + emit d->machine->waitForSnackbar(); + } +} diff --git a/components/snackbar.h b/components/snackbar.h index 840b4ea..3745ec8 100644 --- a/components/snackbar.h +++ b/components/snackbar.h @@ -3,17 +3,51 @@ #include +class SnackbarPrivate; +class SnackbarStateMachine; + class Snackbar : public QWidget { Q_OBJECT + Q_PROPERTY(int autoHideDuration WRITE setAutoHideDuration READ autoHideDuration) + public: explicit Snackbar(QWidget *parent = 0); ~Snackbar(); + void setAutoHideDuration(int duration); + int autoHideDuration() const; + + void setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + + void setTextColor(const QColor &color); + QColor textColor() const; + + void setFontSize(qreal size); + qreal fontSize() const; + + void setBoxWidth(int width); + int boxWidth() const; + +public slots: + void addMessage(const QString &message, bool instant = false); + +protected: + bool event(QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + + void dequeue(); + + const QScopedPointer d_ptr; + private: Q_DISABLE_COPY(Snackbar) - //Q_DECLARE_PRIVATE(Snackbar) + Q_DECLARE_PRIVATE(Snackbar) + + friend class SnackbarStateMachine; }; #endif // SNACKBAR_H diff --git a/components/snackbar_internal.cpp b/components/snackbar_internal.cpp new file mode 100644 index 0000000..b301154 --- /dev/null +++ b/components/snackbar_internal.cpp @@ -0,0 +1,89 @@ +#include "snackbar_internal.h" +#include +#include +#include +#include "snackbar.h" + +SnackbarStateMachine::SnackbarStateMachine(Snackbar *parent) + : QStateMachine(parent), + snackbar(parent), + _offset(0) +{ + timer.setSingleShot(true); + + QState *hiddenState = new QState; + QState *visibleState = new QState; + QState *finalState = new QState; + + addState(hiddenState); + addState(visibleState); + addState(finalState); + + setInitialState(hiddenState); + + QSignalTransition *transition; + + transition = new QSignalTransition(this, SIGNAL(showSnackbar())); + transition->setTargetState(visibleState); + hiddenState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(hideSnackbar())); + transition->setTargetState(visibleState); + hiddenState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(hideSnackbar())); + transition->setTargetState(finalState); + visibleState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(waitForSnackbar())); + transition->setTargetState(hiddenState); + finalState->addTransition(transition); + + transition = new QSignalTransition(this, SIGNAL(showNextSnackbar())); + transition->setTargetState(visibleState); + finalState->addTransition(transition); + + connect(visibleState, SIGNAL(propertiesAssigned()), + this, SLOT(snackbarShown())); + connect(finalState, SIGNAL(propertiesAssigned()), + this, SLOT(snackbarHidden())); + + QPropertyAnimation *animation; + + animation = new QPropertyAnimation(this, "offset"); + animation->setEasingCurve(QEasingCurve::OutCubic); + animation->setDuration(400); + addDefaultAnimation(animation); + + hiddenState->assignProperty(this, "offset", 1); + visibleState->assignProperty(this, "offset", 0); + finalState->assignProperty(this, "offset", 1); + + connect(&timer, SIGNAL(timeout()), this, SIGNAL(hideSnackbar())); + connect(this, SIGNAL(hideSnackbar()), &timer, SLOT(stop())); +} + +SnackbarStateMachine::~SnackbarStateMachine() +{ +} + +void SnackbarStateMachine::setOffset(qreal offset) +{ + _offset = offset; + snackbar->update(); +} + +qreal SnackbarStateMachine::offset() const +{ + return _offset; +} + +void SnackbarStateMachine::snackbarHidden() +{ + snackbar->dequeue(); +} + +void SnackbarStateMachine::snackbarShown() +{ + timer.start(snackbar->autoHideDuration()); +} diff --git a/components/snackbar_internal.h b/components/snackbar_internal.h new file mode 100644 index 0000000..f2e24b0 --- /dev/null +++ b/components/snackbar_internal.h @@ -0,0 +1,40 @@ +#ifndef SNACKBAR_INTERNAL_H +#define SNACKBAR_INTERNAL_H + +#include +#include + +class Snackbar; + +class SnackbarStateMachine : public QStateMachine +{ + Q_OBJECT + + Q_PROPERTY(qreal offset WRITE setOffset READ offset) + +public: + SnackbarStateMachine(Snackbar *parent); + ~SnackbarStateMachine(); + + void setOffset(qreal offset); + qreal offset() const; + +protected slots: + void snackbarHidden(); + void snackbarShown(); + +signals: + void showSnackbar(); + void hideSnackbar(); + void waitForSnackbar(); + void showNextSnackbar(); + +private: + Q_DISABLE_COPY(SnackbarStateMachine) + + Snackbar *const snackbar; + QTimer timer; + qreal _offset; +}; + +#endif // SNACKBAR_INTERNAL_H diff --git a/components/snackbar_p.h b/components/snackbar_p.h index f6ca730..8b863f3 100644 --- a/components/snackbar_p.h +++ b/components/snackbar_p.h @@ -1,4 +1,29 @@ #ifndef SNACKBAR_P_H #define SNACKBAR_P_H +#include + +class Snackbar; +class SnackbarStateMachine; + +class SnackbarPrivate +{ + Q_DISABLE_COPY(SnackbarPrivate) + Q_DECLARE_PUBLIC(Snackbar) + +public: + SnackbarPrivate(Snackbar *q); + ~SnackbarPrivate(); + + void init(); + + Snackbar *const q_ptr; + SnackbarStateMachine *machine; + QColor backgroundColor; + QColor textColor; + QList messages; + int duration; + int boxWidth; +}; + #endif // SNACKBAR_P_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b649322..74bd108 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "mainwindow.h" #include "examples/about.h" @@ -21,6 +22,7 @@ #include "examples/menuexamples.h" #include "examples/iconmenuexamples.h" #include "components/fab.h" +#include "components/snackbar.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -54,10 +56,28 @@ MainWindow::MainWindow(QWidget *parent) // - FloatingActionButton *button2 = new FloatingActionButton(QIcon("../qt-material-widgets/ic_message_white_24px.svg")); - button2->setParent(this); + new FloatingActionButton(QIcon("../qt-material-widgets/ic_message_white_24px.svg"), this); //button2->setDisabled(true); + + snackbar = new Snackbar; + snackbar->setParent(this); + + // + + QPushButton *btn = new QPushButton; + btn->setText("Show Snackbar"); + btn->setGeometry(90, 50, 140, 40); + btn->setParent(this); + + connect(btn, SIGNAL(pressed()), this, SLOT(addMsg())); + + btn = new QPushButton; + btn->setText("Show Snackbar (instant)"); + btn->setGeometry(240, 50, 140, 40); + btn->setParent(this); + + connect(btn, SIGNAL(pressed()), this, SLOT(addInstantMsg())); } MainWindow::~MainWindow() @@ -102,6 +122,21 @@ void MainWindow::showWidget(QAction *action) } } +static int n = 1; + +void MainWindow::addMsg() +{ + snackbar->addMessage(QString("Hello from the Snackbar (") % QString::number(n++) % QString(")")); +} + +void MainWindow::addInstantMsg() +{ + QString msg("This is longer message which will show up immediately after it is added to the message queue"); + msg.append(QString(" (") % QString::number(n++) % QString(").")); + + snackbar->addMessage(msg, true); +} + void MainWindow::_initWidget() { QWidget *widget = new QWidget; diff --git a/mainwindow.h b/mainwindow.h index dbed05b..0b07a76 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -20,6 +20,7 @@ class AvatarExamples; class MenuExamples; class IconMenuExamples; class QStackedLayout; +class Snackbar; class MainWindow : public QMainWindow { @@ -31,6 +32,8 @@ public: protected slots: void showWidget(QAction *action); + void addMsg(); + void addInstantMsg(); private: void _initWidget(); @@ -53,6 +56,7 @@ private: MenuExamples *const _menuExamples; IconMenuExamples *const _iconMenuExamples; About *const _about; + Snackbar *snackbar; }; #endif // MAINWINDOW_H diff --git a/qt-material-widgets.pro b/qt-material-widgets.pro index d9dd055..9e27b08 100644 --- a/qt-material-widgets.pro +++ b/qt-material-widgets.pro @@ -64,7 +64,8 @@ SOURCES += main.cpp\ lib/checkable_internal.cpp \ components/snackbar.cpp \ components/textfield_internal.cpp \ - components/drawer.cpp + components/drawer.cpp \ + components/snackbar_internal.cpp HEADERS += mainwindow.h \ components/appbar.h \ @@ -137,7 +138,8 @@ HEADERS += mainwindow.h \ components/textfield_internal.h \ components/badge_p.h \ components/drawer.h \ - components/avatar_p.h + components/avatar_p.h \ + components/snackbar_internal.h RESOURCES += \ resources.qrc