Add toast notification control.

This commit is contained in:
John Preston 2019-09-20 11:04:55 +03:00
parent d3e8eb4292
commit d22f5f405f
7 changed files with 462 additions and 0 deletions

View file

@ -56,6 +56,12 @@
<(src_loc)/ui/text/text_isolated_emoji.h
<(src_loc)/ui/text/text_utilities.cpp
<(src_loc)/ui/text/text_utilities.h
<(src_loc)/ui/toast/toast.cpp
<(src_loc)/ui/toast/toast.h
<(src_loc)/ui/toast/toast_manager.cpp
<(src_loc)/ui/toast/toast_manager.h
<(src_loc)/ui/toast/toast_widget.cpp
<(src_loc)/ui/toast/toast_widget.h
<(src_loc)/ui/widgets/box_content_divider.cpp
<(src_loc)/ui/widgets/box_content_divider.h
<(src_loc)/ui/widgets/buttons.cpp

84
ui/toast/toast.cpp Normal file
View file

@ -0,0 +1,84 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/toast/toast.h"
#include "ui/toast/toast_manager.h"
#include "ui/toast/toast_widget.h"
namespace Ui {
namespace Toast {
namespace {
QPointer<QWidget> DefaultParent;
} // namespace
Instance::Instance(
const Config &config,
not_null<QWidget*> widgetParent,
const Private &)
: _hideAtMs(crl::now() + config.durationMs) {
_widget = std::make_unique<internal::Widget>(widgetParent, config);
_a_opacity.start(
[=] { opacityAnimationCallback(); },
0.,
1.,
st::toastFadeInDuration);
}
void SetDefaultParent(not_null<QWidget*> parent) {
DefaultParent = parent;
}
void Show(not_null<QWidget*> parent, const Config &config) {
const auto manager = internal::Manager::instance(parent);
manager->addToast(std::make_unique<Instance>(
config,
parent,
Instance::Private()));
}
void Show(const Config &config) {
if (const auto parent = DefaultParent.data()) {
Show(parent, config);
}
}
void Show(not_null<QWidget*> parent, const QString &text) {
auto config = Config();
config.text = text;
Show(parent, config);
}
void Show(const QString &text) {
auto config = Config();
config.text = text;
Show(config);
}
void Instance::opacityAnimationCallback() {
_widget->setShownLevel(_a_opacity.value(_hiding ? 0. : 1.));
_widget->update();
if (!_a_opacity.animating()) {
if (_hiding) {
hide();
}
}
}
void Instance::hideAnimated() {
_hiding = true;
_a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., st::toastFadeOutDuration);
}
void Instance::hide() {
_widget->hide();
_widget->deleteLater();
}
} // namespace Toast
} // namespace Ui

66
ui/toast/toast.h Normal file
View file

@ -0,0 +1,66 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/effects/animations.h"
namespace Ui {
namespace Toast {
namespace internal {
class Manager;
class Widget;
} // namespace internal
static constexpr const int DefaultDuration = 1500;
struct Config {
QString text;
QMargins padding;
int durationMs = DefaultDuration;
int minWidth = 0;
int maxWidth = 0;
int maxLines = 16;
bool multiline = false;
};
void SetDefaultParent(not_null<QWidget*> parent);
void Show(not_null<QWidget*> parent, const Config &config);
void Show(const Config &config);
void Show(not_null<QWidget*> parent, const QString &text);
void Show(const QString &text);
class Instance {
struct Private {
};
public:
Instance(
const Config &config,
not_null<QWidget*> widgetParent,
const Private &);
Instance(const Instance &other) = delete;
Instance &operator=(const Instance &other) = delete;
void hideAnimated();
void hide();
private:
void opacityAnimationCallback();
bool _hiding = false;
Ui::Animations::Simple _a_opacity;
const crl::time _hideAtMs;
// ToastManager should reset _widget pointer if _widget is destroyed.
friend class internal::Manager;
friend void Show(not_null<QWidget*> parent, const Config &config);
std::unique_ptr<internal::Widget> _widget;
};
} // namespace Toast
} // namespace Ui

127
ui/toast/toast_manager.cpp Normal file
View file

@ -0,0 +1,127 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/toast/toast_manager.h"
#include "ui/toast/toast_widget.h"
namespace Ui {
namespace Toast {
namespace internal {
namespace {
base::flat_map<not_null<QObject*>, not_null<Manager*>> ManagersMap;
} // namespace
Manager::Manager(not_null<QWidget*> parent, const CreateTag &)
: QObject(parent)
, _hideTimer([=] { hideByTimer(); }) {
}
bool Manager::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::Resize) {
for (auto i = _toastByWidget.cbegin(), e = _toastByWidget.cend(); i != e; ++i) {
if (i.key()->parentWidget() == o) {
i.key()->onParentResized();
}
}
}
return QObject::eventFilter(o, e);
}
not_null<Manager*> Manager::instance(not_null<QWidget*> parent) {
auto i = ManagersMap.find(parent);
if (i == end(ManagersMap)) {
i = ManagersMap.emplace(
parent,
Ui::CreateChild<Manager>(parent.get(), CreateTag())
).first;
}
return i->second;
}
void Manager::addToast(std::unique_ptr<Instance> &&toast) {
_toasts.push_back(toast.release());
Instance *t = _toasts.back();
Widget *widget = t->_widget.get();
_toastByWidget.insert(widget, t);
connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(onToastWidgetDestroyed(QObject*)));
if (auto parent = widget->parentWidget()) {
auto found = false;
for (auto i = _toastParents.begin(); i != _toastParents.cend();) {
if (*i == parent) {
found = true;
break;
} else if (!*i) {
i = _toastParents.erase(i);
} else {
++i;
}
}
if (!found) {
_toastParents.insert(parent);
parent->installEventFilter(this);
}
}
auto oldHideNearestMs = _toastByHideTime.isEmpty() ? 0LL : _toastByHideTime.firstKey();
_toastByHideTime.insert(t->_hideAtMs, t);
if (!oldHideNearestMs || _toastByHideTime.firstKey() < oldHideNearestMs) {
startNextHideTimer();
}
}
void Manager::hideByTimer() {
auto now = crl::now();
for (auto i = _toastByHideTime.begin(); i != _toastByHideTime.cend();) {
if (i.key() <= now) {
auto toast = i.value();
i = _toastByHideTime.erase(i);
toast->hideAnimated();
} else {
break;
}
}
startNextHideTimer();
}
void Manager::onToastWidgetDestroyed(QObject *widget) {
auto i = _toastByWidget.find(static_cast<Widget*>(widget));
if (i != _toastByWidget.cend()) {
auto toast = i.value();
_toastByWidget.erase(i);
toast->_widget.release();
int index = _toasts.indexOf(toast);
if (index >= 0) {
_toasts.removeAt(index);
delete toast;
}
}
}
void Manager::startNextHideTimer() {
if (_toastByHideTime.isEmpty()) return;
auto ms = crl::now();
if (ms >= _toastByHideTime.firstKey()) {
crl::on_main(this, [=] {
hideByTimer();
});
} else {
_hideTimer.callOnce(_toastByHideTime.firstKey() - ms);
}
}
Manager::~Manager() {
ManagersMap.remove(parent());
}
} // namespace internal
} // namespace Toast
} // namespace Ui

55
ui/toast/toast_manager.h Normal file
View file

@ -0,0 +1,55 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/toast/toast.h"
#include "base/timer.h"
namespace Ui {
namespace Toast {
namespace internal {
class Widget;
class Manager : public QObject {
Q_OBJECT
struct CreateTag {
};
public:
Manager(not_null<QWidget*> parent, const CreateTag &);
Manager(const Manager &other) = delete;
Manager &operator=(const Manager &other) = delete;
~Manager();
static not_null<Manager*> instance(not_null<QWidget*> parent);
void addToast(std::unique_ptr<Instance> &&toast);
protected:
bool eventFilter(QObject *o, QEvent *e);
private slots:
void onToastWidgetDestroyed(QObject *widget);
private:
void startNextHideTimer();
void hideByTimer();
base::Timer _hideTimer;
crl::time _nextHide = 0;
QMultiMap<crl::time, Instance*> _toastByHideTime;
QMap<Widget*, Instance*> _toastByWidget;
QList<Instance*> _toasts;
OrderedSet<QPointer<QWidget>> _toastParents;
};
} // namespace internal
} // namespace Toast
} // namespace Ui

73
ui/toast/toast_widget.cpp Normal file
View file

@ -0,0 +1,73 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/toast/toast_widget.h"
#include "ui/image/image_prepare.h"
#include "styles/palette.h"
namespace Ui {
namespace Toast {
namespace internal {
Widget::Widget(QWidget *parent, const Config &config)
: TWidget(parent)
, _roundRect(ImageRoundRadius::Large, st::toastBg)
, _multiline(config.multiline)
, _maxWidth((config.maxWidth > 0) ? config.maxWidth : st::toastMaxWidth)
, _padding((config.padding.left() > 0) ? config.padding : st::toastPadding)
, _maxTextWidth(widthWithoutPadding(_maxWidth))
, _maxTextHeight(
st::toastTextStyle.font->height * (_multiline ? config.maxLines : 1))
, _text(_multiline ? widthWithoutPadding(config.minWidth) : QFIXED_MAX) {
const auto toastOptions = TextParseOptions{
TextParseMultiline,
_maxTextWidth,
_maxTextHeight,
Qt::LayoutDirectionAuto
};
_text.setText(
st::toastTextStyle,
_multiline ? config.text : TextUtilities::SingleLine(config.text),
toastOptions);
setAttribute(Qt::WA_TransparentForMouseEvents);
onParentResized();
show();
}
void Widget::onParentResized() {
auto newWidth = _maxWidth;
accumulate_min(newWidth, _padding.left() + _text.maxWidth() + _padding.right());
accumulate_min(newWidth, parentWidget()->width() - 2 * st::toastMinMargin);
_textWidth = widthWithoutPadding(newWidth);
const auto textHeight = _multiline
? qMin(_text.countHeight(_textWidth), _maxTextHeight)
: _text.minHeight();
const auto newHeight = _padding.top() + textHeight + _padding.bottom();
setGeometry((parentWidget()->width() - newWidth) / 2, (parentWidget()->height() - newHeight) / 2, newWidth, newHeight);
}
void Widget::setShownLevel(float64 shownLevel) {
_shownLevel = shownLevel;
}
void Widget::paintEvent(QPaintEvent *e) {
Painter p(this);
PainterHighQualityEnabler hq(p);
p.setOpacity(_shownLevel);
_roundRect.paint(p, rect());
const auto lines = _maxTextHeight / st::toastTextStyle.font->height;
p.setPen(st::toastFg);
_text.drawElided(p, _padding.left(), _padding.top(), _textWidth + 1, lines);
}
} // namespace internal
} // namespace Toast
} // namespace Ui

51
ui/toast/toast_widget.h Normal file
View file

@ -0,0 +1,51 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/toast/toast.h"
#include "ui/text/text.h"
#include "ui/rp_widget.h"
#include "ui/round_rect.h"
namespace Ui {
namespace Toast {
namespace internal {
class Widget : public TWidget {
public:
Widget(QWidget *parent, const Config &config);
// shownLevel=1 completely visible, shownLevel=0 completely invisible
void setShownLevel(float64 shownLevel);
void onParentResized();
protected:
void paintEvent(QPaintEvent *e) override;
private:
int widthWithoutPadding(int w) {
return w - _padding.left() - _padding.right();
}
RoundRect _roundRect;
float64 _shownLevel = 0;
bool _multiline = false;
int _maxWidth = 0;
QMargins _padding;
int _maxTextWidth = 0;
int _maxTextHeight = 0;
int _textWidth = 0;
Text::String _text;
};
} // namespace internal
} // namespace Toast
} // namespace Ui