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 <QDebug>
+#include <QPainter>
+
+#include <tweeny/tweeny.h>
+
+#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_);
+}
|