diff --git a/components/qtmaterialsnackbar.cpp b/components/qtmaterialsnackbar.cpp new file mode 100644 index 0000000..a45902c --- /dev/null +++ b/components/qtmaterialsnackbar.cpp @@ -0,0 +1,267 @@ +#include "xx/qtmaterialsnackbar.h" +#include "xx/qtmaterialsnackbar_p.h" +#include +#include +#include +#include "xx/qtmaterialsnackbar_internal.h" +#include "xxlib/qtmaterialstyle.h" +#include "xxlib/qtmaterialstatetransition.h" +#include + +/*! + * \class QtMaterialSnackbarPrivate + * \internal + */ + +QtMaterialSnackbarPrivate::QtMaterialSnackbarPrivate(QtMaterialSnackbar *q) + : q_ptr(q) +{ +} + +QtMaterialSnackbarPrivate::~QtMaterialSnackbarPrivate() +{ +} + +void QtMaterialSnackbarPrivate::init() +{ + Q_Q(QtMaterialSnackbar); + + stateMachine = new QtMaterialSnackbarStateMachine(q); + bgOpacity = 0.9; + duration = 3000; + boxWidth = 300; + clickDismiss = false; + useThemeColors = true; + + q->setAttribute(Qt::WA_TransparentForMouseEvents); + + QFontDatabase db; + QFont font(db.font("Roboto", "Medium", 10)); + font.setCapitalization(QFont::AllUppercase); + q->setFont(font); + + stateMachine->start(); + QCoreApplication::processEvents(); +} + +/*! + * \class QtMaterialSnackbar + */ + +QtMaterialSnackbar::QtMaterialSnackbar(QWidget *parent) + : QtMaterialOverlayWidget(parent), + d_ptr(new QtMaterialSnackbarPrivate(this)) +{ + d_func()->init(); +} + +QtMaterialSnackbar::~QtMaterialSnackbar() +{ +} + +void QtMaterialSnackbar::setAutoHideDuration(int duration) +{ + Q_D(QtMaterialSnackbar); + + d->duration = duration; +} + +int QtMaterialSnackbar::autoHideDuration() const +{ + Q_D(const QtMaterialSnackbar); + + return d->duration; +} + +void QtMaterialSnackbar::setUseThemeColors(bool value) +{ + Q_D(QtMaterialSnackbar); + + if (d->useThemeColors == value) { + return; + } + + d->useThemeColors = value; + update(); +} + +bool QtMaterialSnackbar::useThemeColors() const +{ + Q_D(const QtMaterialSnackbar); + + return d->useThemeColors; +} + +void QtMaterialSnackbar::setBackgroundColor(const QColor &color) +{ + Q_D(QtMaterialSnackbar); + + d->backgroundColor = color; + setUseThemeColors(false); +} + +QColor QtMaterialSnackbar::backgroundColor() const +{ + Q_D(const QtMaterialSnackbar); + + if (d->useThemeColors || !d->backgroundColor.isValid()) { + return QtMaterialStyle::instance().themeColor("text"); + } else { + return d->backgroundColor; + } +} + +void QtMaterialSnackbar::setBackgroundOpacity(qreal opacity) +{ + Q_D(QtMaterialSnackbar); + + d->bgOpacity = opacity; + update(); +} + +qreal QtMaterialSnackbar::backgroundOpacity() const +{ + Q_D(const QtMaterialSnackbar); + + return d->bgOpacity; +} + +void QtMaterialSnackbar::setTextColor(const QColor &color) +{ + Q_D(QtMaterialSnackbar); + + d->textColor = color; + setUseThemeColors(false); +} + +QColor QtMaterialSnackbar::textColor() const +{ + Q_D(const QtMaterialSnackbar); + + if (d->useThemeColors || !d->textColor.isValid()) { + return QtMaterialStyle::instance().themeColor("canvas"); + } else { + return d->textColor; + } +} + +void QtMaterialSnackbar::setFontSize(qreal size) +{ + QFont f(font()); + f.setPointSizeF(size); + setFont(f); + + update(); +} + +qreal QtMaterialSnackbar::fontSize() const +{ + return font().pointSizeF(); +} + +void QtMaterialSnackbar::setBoxWidth(int width) +{ + Q_D(QtMaterialSnackbar); + + d->boxWidth = width; + update(); +} + +int QtMaterialSnackbar::boxWidth() const +{ + Q_D(const QtMaterialSnackbar); + + return d->boxWidth; +} + +void QtMaterialSnackbar::setClickToDismissMode(bool value) +{ + Q_D(QtMaterialSnackbar); + + d->clickDismiss = value; +} + +bool QtMaterialSnackbar::clickToDismissMode() const +{ + Q_D(const QtMaterialSnackbar); + + return d->clickDismiss; +} + +void QtMaterialSnackbar::addMessage(const QString &message) +{ + Q_D(QtMaterialSnackbar); + + d->messages.push_back(message); + d->stateMachine->postEvent(new QtMaterialStateTransitionEvent(SnackbarShowTransition)); +} + +void QtMaterialSnackbar::addInstantMessage(const QString &message) +{ + Q_D(QtMaterialSnackbar); + + if (d->messages.isEmpty()) { + d->messages.push_back(message); + } else { + d->messages.insert(1, message); + } + + d->stateMachine->progress(); +} + +void QtMaterialSnackbar::dequeue() +{ + Q_D(QtMaterialSnackbar); + + if (d->messages.isEmpty()) { + return; + } + + d->messages.removeFirst(); + + if (!d->messages.isEmpty()) { + d->stateMachine->postEvent(new QtMaterialStateTransitionEvent(SnackbarNextTransition)); + } else { + d->stateMachine->postEvent(new QtMaterialStateTransitionEvent(SnackbarWaitTransition)); + } +} + +void QtMaterialSnackbar::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + Q_D(QtMaterialSnackbar); + + if (d->messages.isEmpty()) { + return; + } + + QString message = d->messages.first(); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor()); + painter.setBrush(brush); + painter.setOpacity(d->bgOpacity); + + QRect r(0, 0, d->boxWidth, 40); + + painter.setPen(Qt::white); + QRect br = painter.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message); + + painter.setPen(Qt::NoPen); + r = br.united(r).adjusted(-10, -10, 10, 20); + + const qreal s = 1-d->stateMachine->offset(); + + painter.translate((width()-(r.width()-20))/2, + height()+10-s*(r.height())); + + br.moveCenter(r.center()); + painter.drawRoundedRect(r.adjusted(0, 0, 0, 3), 3, 3); + painter.setPen(textColor()); + painter.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message); +} diff --git a/components/qtmaterialsnackbar.h b/components/qtmaterialsnackbar.h new file mode 100644 index 0000000..8631891 --- /dev/null +++ b/components/qtmaterialsnackbar.h @@ -0,0 +1,57 @@ +#ifndef QTMATERIALSNACKBAR_H +#define QTMATERIALSNACKBAR_H + +#include "xxlib/qtmaterialoverlaywidget.h" + +class QtMaterialSnackbarPrivate; + +class QtMaterialSnackbar : public QtMaterialOverlayWidget +{ + Q_OBJECT + +public: + explicit QtMaterialSnackbar(QWidget *parent = 0); + ~QtMaterialSnackbar(); + + void setAutoHideDuration(int duration); + int autoHideDuration() const; + + void setUseThemeColors(bool value); + bool useThemeColors() const; + + void setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + + void setBackgroundOpacity(qreal opacity); + qreal backgroundOpacity() const; + + void setTextColor(const QColor &color); + QColor textColor() const; + + void setFontSize(qreal size); + qreal fontSize() const; + + void setBoxWidth(int width); + int boxWidth() const; + + void setClickToDismissMode(bool value); + bool clickToDismissMode() const; + +public slots: + void addMessage(const QString &message); + void addInstantMessage(const QString &message); + +protected slots: + void dequeue(); + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + + const QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QtMaterialSnackbar) + Q_DECLARE_PRIVATE(QtMaterialSnackbar) +}; + +#endif // QTMATERIALSNACKBAR_H diff --git a/components/qtmaterialsnackbar_internal.cpp b/components/qtmaterialsnackbar_internal.cpp new file mode 100644 index 0000000..3c7ef12 --- /dev/null +++ b/components/qtmaterialsnackbar_internal.cpp @@ -0,0 +1,99 @@ +#include "xx/qtmaterialsnackbar_internal.h" +#include +#include "xx/qtmaterialsnackbar.h" +#include "xxlib/qtmaterialstatetransition.h" +#include + +QtMaterialSnackbarStateMachine::QtMaterialSnackbarStateMachine(QtMaterialSnackbar *parent) + : QStateMachine(parent), + m_snackbar(parent) +{ + m_timer.setSingleShot(true); + + QState *hiddenState = new QState; + QState *visibleState = new QState; + QState *finalState = new QState; + + addState(hiddenState); + addState(visibleState); + addState(finalState); + + setInitialState(hiddenState); + + QtMaterialStateTransition *transition; + + transition = new QtMaterialStateTransition(SnackbarShowTransition); + transition->setTargetState(visibleState); + hiddenState->addTransition(transition); + + transition = new QtMaterialStateTransition(SnackbarHideTransition); + transition->setTargetState(visibleState); + hiddenState->addTransition(transition); + + transition = new QtMaterialStateTransition(SnackbarHideTransition); + transition->setTargetState(finalState); + visibleState->addTransition(transition); + + transition = new QtMaterialStateTransition(SnackbarWaitTransition); + transition->setTargetState(hiddenState); + finalState->addTransition(transition); + + transition = new QtMaterialStateTransition(SnackbarNextTransition); + transition->setTargetState(visibleState); + finalState->addTransition(transition); + + connect(visibleState, SIGNAL(propertiesAssigned()), + this, SLOT(snackbarShown())); + connect(finalState, SIGNAL(propertiesAssigned()), + m_snackbar, SLOT(dequeue())); + + QPropertyAnimation *animation; + + animation = new QPropertyAnimation(this, "offset", this); + 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(&m_timer, SIGNAL(timeout()), this, SLOT(progress())); + + m_snackbar->installEventFilter(this); +} + +QtMaterialSnackbarStateMachine::~QtMaterialSnackbarStateMachine() +{ +} + +bool QtMaterialSnackbarStateMachine::eventFilter(QObject *watched, QEvent *event) +{ + if (QEvent::MouseButtonPress == event->type() && m_snackbar->clickToDismissMode()) { + progress(); + } + return QStateMachine::eventFilter(watched, event); +} + +void QtMaterialSnackbarStateMachine::setOffset(qreal offset) +{ + m_offset = offset; + m_snackbar->update(); +} + +void QtMaterialSnackbarStateMachine::progress() +{ + m_timer.stop(); + postEvent(new QtMaterialStateTransitionEvent(SnackbarHideTransition)); + if (m_snackbar->clickToDismissMode()) { + m_snackbar->setAttribute(Qt::WA_TransparentForMouseEvents, true); + } +} + +void QtMaterialSnackbarStateMachine::snackbarShown() +{ + m_timer.start(m_snackbar->autoHideDuration()); + if (m_snackbar->clickToDismissMode()) { + m_snackbar->setAttribute(Qt::WA_TransparentForMouseEvents, false); + } +} diff --git a/components/qtmaterialsnackbar_internal.h b/components/qtmaterialsnackbar_internal.h new file mode 100644 index 0000000..117a8e5 --- /dev/null +++ b/components/qtmaterialsnackbar_internal.h @@ -0,0 +1,44 @@ +#ifndef QTMATERIALSNACKBAR_INTERNAL_H +#define QTMATERIALSNACKBAR_INTERNAL_H + +#include +#include + +class QtMaterialSnackbar; + +class QtMaterialSnackbarStateMachine : public QStateMachine +{ + Q_OBJECT + + Q_PROPERTY(qreal offset WRITE setOffset READ offset) + +public: + QtMaterialSnackbarStateMachine(QtMaterialSnackbar *parent); + ~QtMaterialSnackbarStateMachine(); + + void setOffset(qreal offset); + inline qreal offset() const; + +public slots: + void progress(); + +protected slots: + void snackbarShown(); + +protected: + bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QtMaterialSnackbarStateMachine) + + QtMaterialSnackbar *const m_snackbar; + QTimer m_timer; + qreal m_offset; +}; + +inline qreal QtMaterialSnackbarStateMachine::offset() const +{ + return m_offset; +} + +#endif // QTMATERIALSNACKBAR_INTERNAL_H diff --git a/components/qtmaterialsnackbar_p.h b/components/qtmaterialsnackbar_p.h new file mode 100644 index 0000000..39501fd --- /dev/null +++ b/components/qtmaterialsnackbar_p.h @@ -0,0 +1,33 @@ +#ifndef QTMATERIALSNACKBAR_P_H +#define QTMATERIALSNACKBAR_P_H + +#include +#include + +class QtMaterialSnackbar; +class QtMaterialSnackbarStateMachine; + +class QtMaterialSnackbarPrivate +{ + Q_DISABLE_COPY(QtMaterialSnackbarPrivate) + Q_DECLARE_PUBLIC(QtMaterialSnackbar) + +public: + QtMaterialSnackbarPrivate(QtMaterialSnackbar *q); + ~QtMaterialSnackbarPrivate(); + + void init(); + + QtMaterialSnackbar *const q_ptr; + QtMaterialSnackbarStateMachine *stateMachine; + QColor backgroundColor; + QColor textColor; + qreal bgOpacity; + QList messages; + int duration; + int boxWidth; + bool clickDismiss; + bool useThemeColors; +}; + +#endif // QTMATERIALSNACKBAR_P_H