From 7849f0561b7d8d0e51e3fec3dcfe07e1502c82cc Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Apr 2020 13:16:15 +0400 Subject: [PATCH] Allow sliding toasts from the parent side. --- ui/toast/toast.cpp | 49 +++++++++++++++++------------ ui/toast/toast.h | 36 +++++++++++++++------- ui/toast/toast_manager.cpp | 23 +++++++++++--- ui/toast/toast_manager.h | 10 ++---- ui/toast/toast_widget.cpp | 63 ++++++++++++++++++++++++++++++-------- ui/toast/toast_widget.h | 4 ++- ui/widgets/widgets.style | 2 +- 7 files changed, 130 insertions(+), 57 deletions(-) diff --git a/ui/toast/toast.cpp b/ui/toast/toast.cpp index 8bb9879..b2016ba 100644 --- a/ui/toast/toast.cpp +++ b/ui/toast/toast.cpp @@ -23,45 +23,50 @@ Instance::Instance( not_null widgetParent, const Private &) : _st(config.st) -, _hideAtMs(crl::now() + config.durationMs) { - _widget = std::make_unique(widgetParent, config); - _a_opacity.start( - [=] { opacityAnimationCallback(); }, +, _hideAt(crl::now() + config.durationMs) +, _sliding(config.slideSide != RectPart::None) +, _widget(std::make_unique(widgetParent, config)) { + _shownAnimation.start( + [=] { shownAnimationCallback(); }, 0., 1., - _st->durationFadeIn); + _sliding ? _st->durationSlide : _st->durationFadeIn); } void SetDefaultParent(not_null parent) { DefaultParent = parent; } -void Show(not_null parent, const Config &config) { +base::weak_ptr Show( + not_null parent, + const Config &config) { const auto manager = internal::Manager::instance(parent); - manager->addToast(std::make_unique( + return manager->addToast(std::make_unique( config, parent, Instance::Private())); } -void Show(const Config &config) { +base::weak_ptr Show(const Config &config) { if (const auto parent = DefaultParent.data()) { - Show(parent, config); + return Show(parent, config); } + return nullptr; } -void Show(not_null parent, const QString &text) { - Show(parent, Config{ .text = { text }, .st = &st::defaultToast }); +base::weak_ptr Show( + not_null parent, + const QString &text) { + return Show(parent, Config{ .text = { text }, .st = &st::defaultToast }); } -void Show(const QString &text) { - Show(Config{ .text = { text }, .st = &st::defaultToast }); +base::weak_ptr Show(const QString &text) { + return Show(Config{ .text = { text }, .st = &st::defaultToast }); } -void Instance::opacityAnimationCallback() { - _widget->setShownLevel(_a_opacity.value(_hiding ? 0. : 1.)); - _widget->update(); - if (!_a_opacity.animating()) { +void Instance::shownAnimationCallback() { + _widget->setShownLevel(_shownAnimation.value(_hiding ? 0. : 1.)); + if (!_shownAnimation.animating()) { if (_hiding) { hide(); } @@ -70,11 +75,11 @@ void Instance::opacityAnimationCallback() { void Instance::hideAnimated() { _hiding = true; - _a_opacity.start( - [=] { opacityAnimationCallback(); }, + _shownAnimation.start( + [=] { shownAnimationCallback(); }, 1., 0., - _st->durationFadeOut); + _sliding ? _st->durationSlide : _st->durationFadeOut); } void Instance::hide() { @@ -82,5 +87,9 @@ void Instance::hide() { _widget->deleteLater(); } +not_null Instance::widget() const { + return _widget.get(); +} + } // namespace Toast } // namespace Ui diff --git a/ui/toast/toast.h b/ui/toast/toast.h index 43fef3a..6c2a67c 100644 --- a/ui/toast/toast.h +++ b/ui/toast/toast.h @@ -9,6 +9,9 @@ #include "ui/effects/animations.h" #include "ui/text/text_entity.h" #include "ui/click_handler.h" +#include "ui/rect_part.h" +#include "ui/rp_widget.h" +#include "base/weak_ptr.h" namespace style { struct Toast; @@ -32,15 +35,13 @@ struct Config { int maxLines = 16; bool multiline = false; bool dark = false; + RectPart slideSide = RectPart::None; ClickHandlerFilter filter; }; -void SetDefaultParent(not_null parent); -void Show(not_null parent, const Config &config); -void Show(const Config &config); -void Show(not_null parent, const QString &text); -void Show(const QString &text); -class Instance { +void SetDefaultParent(not_null parent); + +class Instance final : public base::has_weak_ptr { struct Private { }; @@ -55,22 +56,35 @@ public: void hideAnimated(); void hide(); + [[nodiscard]] not_null widget() const; + private: - void opacityAnimationCallback(); + void shownAnimationCallback(); const not_null _st; + const crl::time _hideAt = 0; + Ui::Animations::Simple _shownAnimation; bool _hiding = false; - Ui::Animations::Simple _a_opacity; - - const crl::time _hideAtMs; + bool _sliding = false; // ToastManager should reset _widget pointer if _widget is destroyed. friend class internal::Manager; - friend void Show(not_null parent, const Config &config); + friend base::weak_ptr Show( + not_null parent, + const Config &config); std::unique_ptr _widget; }; +base::weak_ptr Show( + not_null parent, + const Config &config); +base::weak_ptr Show(const Config &config); +base::weak_ptr Show( + not_null parent, + const QString &text); +base::weak_ptr Show(const QString &text); + } // namespace Toast } // namespace Ui diff --git a/ui/toast/toast_manager.cpp b/ui/toast/toast_manager.cpp index adbac76..165af03 100644 --- a/ui/toast/toast_manager.cpp +++ b/ui/toast/toast_manager.cpp @@ -44,13 +44,16 @@ not_null Manager::instance(not_null parent) { return i->second; } -void Manager::addToast(std::unique_ptr &&toast) { +base::weak_ptr Manager::addToast( + std::unique_ptr &&toast) { _toasts.push_back(std::move(toast)); const auto t = _toasts.back().get(); const auto widget = t->_widget.get(); _toastByWidget.emplace(widget, t); - connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(onToastWidgetDestroyed(QObject*))); + connect(widget, &QObject::destroyed, [=] { + toastWidgetDestroyed(widget); + }); if (const auto parent = widget->parentWidget()) { auto found = false; for (auto i = _toastParents.begin(); i != _toastParents.cend();) { @@ -72,10 +75,11 @@ void Manager::addToast(std::unique_ptr &&toast) { const auto nearestHide = _toastByHideTime.empty() ? 0LL : _toastByHideTime.begin()->first; - _toastByHideTime.emplace(t->_hideAtMs, t); + _toastByHideTime.emplace(t->_hideAt, t); if (!nearestHide || _toastByHideTime.begin()->first < nearestHide) { startNextHideTimer(); } + return make_weak(t); } void Manager::hideByTimer() { @@ -92,7 +96,7 @@ void Manager::hideByTimer() { startNextHideTimer(); } -void Manager::onToastWidgetDestroyed(QObject *widget) { +void Manager::toastWidgetDestroyed(QObject *widget) { const auto i = _toastByWidget.find(static_cast(widget)); if (i == _toastByWidget.cend()) { return; @@ -101,6 +105,13 @@ void Manager::onToastWidgetDestroyed(QObject *widget) { _toastByWidget.erase(i); toast->_widget.release(); + for (auto i = begin(_toastByHideTime); i != end(_toastByHideTime); ++i) { + if (i->second == toast) { + _toastByHideTime.erase(i); + break; + } + } + const auto j = ranges::find( _toasts, toast.get(), @@ -111,7 +122,9 @@ void Manager::onToastWidgetDestroyed(QObject *widget) { } void Manager::startNextHideTimer() { - if (_toastByHideTime.empty()) return; + if (_toastByHideTime.empty()) { + return; + } auto ms = crl::now(); if (ms >= _toastByHideTime.begin()->first) { diff --git a/ui/toast/toast_manager.h b/ui/toast/toast_manager.h index c094260..224b8f2 100644 --- a/ui/toast/toast_manager.h +++ b/ui/toast/toast_manager.h @@ -14,9 +14,7 @@ namespace Toast { namespace internal { class Widget; -class Manager : public QObject { - Q_OBJECT - +class Manager final : public QObject { struct CreateTag { }; @@ -28,15 +26,13 @@ public: static not_null instance(not_null parent); - void addToast(std::unique_ptr &&toast); + base::weak_ptr addToast(std::unique_ptr &&toast); protected: bool eventFilter(QObject *o, QEvent *e); -private slots: - void onToastWidgetDestroyed(QObject *widget); - private: + void toastWidgetDestroyed(QObject *widget); void startNextHideTimer(); void hideByTimer(); diff --git a/ui/toast/toast_widget.cpp b/ui/toast/toast_widget.cpp index b489cd4..e0130a1 100644 --- a/ui/toast/toast_widget.cpp +++ b/ui/toast/toast_widget.cpp @@ -17,9 +17,10 @@ namespace Toast { namespace internal { Widget::Widget(QWidget *parent, const Config &config) -: TWidget(parent) +: RpWidget(parent) , _st(config.st) , _roundRect(ImageRoundRadius::Large, st::toastBg) +, _slideSide(config.slideSide) , _multiline(config.multiline) , _dark(config.dark) , _maxTextWidth(widthWithoutPadding(_st->maxWidth)) @@ -49,36 +50,74 @@ Widget::Widget(QWidget *parent, const Config &config) } void Widget::onParentResized() { - auto newWidth = _st->maxWidth; + updateGeometry(); +} + +void Widget::updateGeometry() { + auto width = _st->maxWidth; accumulate_min( - newWidth, + width, _st->padding.left() + _text.maxWidth() + _st->padding.right()); accumulate_min( - newWidth, + width, parentWidget()->width() - _st->margin.left() - _st->margin.right()); - _textWidth = widthWithoutPadding(newWidth); + _textWidth = widthWithoutPadding(width); const auto textHeight = _multiline ? qMin(_text.countHeight(_textWidth), _maxTextHeight) : _text.minHeight(); - const auto newHeight = _st->padding.top() + const auto height = _st->padding.top() + textHeight + _st->padding.bottom(); - setGeometry( - (parentWidget()->width() - newWidth) / 2, - (parentWidget()->height() - newHeight) / 2, - newWidth, - newHeight); + const auto rect = QRect(0, 0, width, height); + const auto outer = parentWidget()->size(); + const auto full = QPoint(outer.width(), outer.height()); + const auto middle = QPoint( + (outer.width() - width) / 2, + (outer.height() - height) / 2); + const auto interpolated = [&](int from, int to) { + return anim::interpolate(from, to, _shownLevel); + }; + setGeometry(rect.translated([&] { + switch (_slideSide) { + case RectPart::None: + return middle; + case RectPart::Left: + return QPoint( + interpolated(-width, _st->margin.left()), + middle.y()); + case RectPart::Top: + return QPoint( + middle.x(), + interpolated(-height, _st->margin.top())); + case RectPart::Right: + return QPoint( + full.x() - interpolated(0, width + _st->margin.right()), + middle.y()); + case RectPart::Bottom: + return QPoint( + middle.x(), + full.y() - interpolated(0, height + _st->margin.bottom())); + } + Unexpected("Slide side in Toast::Widget::updateGeometry."); + }())); } void Widget::setShownLevel(float64 shownLevel) { _shownLevel = shownLevel; + if (_slideSide != RectPart::None) { + updateGeometry(); + } else { + update(); + } } void Widget::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); - p.setOpacity(_shownLevel); + if (_slideSide == RectPart::None) { + p.setOpacity(_shownLevel); + } _roundRect.paint(p, rect()); if (_dark) { _roundRect.paint(p, rect()); diff --git a/ui/toast/toast_widget.h b/ui/toast/toast_widget.h index 79d91df..431bdfd 100644 --- a/ui/toast/toast_widget.h +++ b/ui/toast/toast_widget.h @@ -15,7 +15,7 @@ namespace Ui { namespace Toast { namespace internal { -class Widget : public TWidget { +class Widget final : public RpWidget { public: Widget(QWidget *parent, const Config &config); @@ -34,9 +34,11 @@ protected: private: [[nodiscard]] int widthWithoutPadding(int w) const; + void updateGeometry(); const not_null _st; RoundRect _roundRect; + RectPart _slideSide = RectPart::None; float64 _shownLevel = 0; bool _multiline = false; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 030af37..d020ef6 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1289,7 +1289,7 @@ defaultToast: Toast { maxWidth: 480px; durationFadeIn: 200; durationFadeOut: 1000; - durationSlide: 400; + durationSlide: 160; } // Windows specific title