Add toast notification control.
This commit is contained in:
parent
d3e8eb4292
commit
d22f5f405f
7 changed files with 462 additions and 0 deletions
|
|
@ -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
84
ui/toast/toast.cpp
Normal 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
66
ui/toast/toast.h
Normal 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
127
ui/toast/toast_manager.cpp
Normal 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
55
ui/toast/toast_manager.h
Normal 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
73
ui/toast/toast_widget.cpp
Normal 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
51
ui/toast/toast_widget.h
Normal 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
|
||||
Loading…
Add table
Reference in a new issue