From 16ce28f4d25e43cb8fe2ec519316940080dd2bda Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Oct 2016 19:41:09 +0300 Subject: [PATCH] Notifications settings done: screen corner selection + max count. --- Telegram/SourceFiles/boxes/boxes.style | 12 + .../SourceFiles/boxes/notifications_box.cpp | 312 +++++++++++++++++- .../SourceFiles/boxes/notifications_box.h | 31 +- Telegram/SourceFiles/facades.cpp | 6 + Telegram/SourceFiles/facades.h | 21 ++ Telegram/SourceFiles/localstorage.cpp | 22 +- .../settings/settings_scale_widget.cpp | 5 +- .../settings/settings_scale_widget.h | 2 +- .../ui/widgets/discrete_slider.cpp | 8 +- .../SourceFiles/ui/widgets/discrete_slider.h | 13 +- .../window/notifications_manager_default.cpp | 237 ++++++++----- .../window/notifications_manager_default.h | 56 ++-- Telegram/SourceFiles/window/window.style | 2 +- 13 files changed, 594 insertions(+), 133 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 971ea807c7..306381f21b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -98,3 +98,15 @@ notificationsBoxScreenSize: size(280px, 160px); notificationsBoxScreenBg: titleBg; notificationsBoxCountLabelTop: 80px; notificationsBoxCountTop: 30px; + +notificationsSampleSkip: 5px; +notificationsSampleTopSkip: 5px; +notificationsSampleBottomSkip: 5px; +notificationsSampleMargin: 2px; + +notificationSampleOpacity: 0.5; +notificationSampleSize: size(64px, 16px); +notificationSampleUserpicFg: #40ace3; +notificationSampleCloseFg: #d7d7d7; +notificationSampleTextFg: #d7d7d7; +notificationSampleNameFg: #939393; diff --git a/Telegram/SourceFiles/boxes/notifications_box.cpp b/Telegram/SourceFiles/boxes/notifications_box.cpp index 74909368c6..2e99624113 100644 --- a/Telegram/SourceFiles/boxes/notifications_box.cpp +++ b/Telegram/SourceFiles/boxes/notifications_box.cpp @@ -25,6 +25,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/buttons/round_button.h" #include "ui/widgets/discrete_slider.h" #include "styles/style_boxes.h" +#include "styles/style_dialogs.h" +#include "styles/style_window.h" +#include "application.h" +#include "localstorage.h" namespace { @@ -32,21 +36,83 @@ constexpr int kMaxNotificationsCount = 5; } // namespace +class NotificationsBox::SampleWidget : public QWidget { +public: + SampleWidget(NotificationsBox *owner, const QPixmap &cache) : QWidget(nullptr) + , _owner(owner) + , _cache(cache) { + resize(cache.width() / cache.devicePixelRatio(), cache.height() / cache.devicePixelRatio()); + + setAttribute(Qt::WA_MacAlwaysShowToolWindow); + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_OpaquePaintEvent); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint); + + setWindowOpacity(0.); + show(); + } + + void detach() { + _owner = nullptr; + hideFast(); + } + + void showFast() { + _hiding = false; + startAnimation(); + } + + void hideFast() { + _hiding = true; + startAnimation(); + } + +protected: + virtual void paintEvent(QPaintEvent *e) { + Painter p(this); + p.drawPixmap(0, 0, _cache); + } + +private: + void startAnimation() { + _opacity.start([this] { animationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., st::notifyFastAnim); + } + void animationCallback() { + setWindowOpacity(_opacity.current(_hiding ? 0. : 1.)); + if (!_opacity.animating() && _hiding) { + if (_owner) { + _owner->removeSample(this); + } + hide(); + deleteLater(); + } + } + + NotificationsBox *_owner; + QPixmap _cache; + FloatAnimation _opacity; + bool _hiding = false; + +}; + NotificationsBox::NotificationsBox() : AbstractBox() +, _chosenCorner(Global::NotificationsCorner()) +, _oldCount(snap(Global::NotificationsCount(), 1, kMaxNotificationsCount)) , _countSlider(this) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { +, _done(this, lang(lng_about_done), st::defaultBoxButton) { + _sampleOpacities.reserve(kMaxNotificationsCount); for (int i = 0; i != kMaxNotificationsCount; ++i) { _countSlider->addSection(QString::number(i + 1)); + _sampleOpacities.push_back(FloatAnimation()); } - _countSlider->setActiveSectionFast(2); + _countSlider->setActiveSectionFast(_oldCount - 1); + _countSlider->setSectionActivatedCallback([this] { countChanged(); }); setMouseTracking(true); - _save->setClickedCallback([this] { - - }); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + _done->setClickedCallback([this] { onClose(); }); + prepareNotificationSampleSmall(); + prepareNotificationSampleLarge(); setMaxHeight(st::notificationsBoxHeight); prepare(); @@ -62,17 +128,55 @@ void NotificationsBox::paintEvent(QPaintEvent *e) { p.setPen(st::boxTitleFg); p.drawTextLeft(contentLeft, st::boxTitlePosition.y(), width(), lang(lng_settings_notifications_position)); - auto screenLeft = (width() - st::notificationsBoxScreenSize.width()) / 2; auto screenRect = getScreenRect(); p.fillRect(screenRect.x(), screenRect.y(), st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height(), st::notificationsBoxScreenBg); auto monitorTop = st::notificationsBoxMonitorTop; st::notificationsBoxMonitor.paint(p, contentLeft, monitorTop, width()); + for (int corner = 0; corner != 4; ++corner) { + auto screenCorner = static_cast(corner); + auto isLeft = Notify::IsLeftCorner(screenCorner); + auto isTop = Notify::IsTopCorner(screenCorner); + auto sampleLeft = isLeft ? (screenRect.x() + st::notificationsSampleSkip) : (screenRect.x() + screenRect.width() - st::notificationsSampleSkip - st::notificationSampleSize.width()); + auto sampleTop = isTop ? (screenRect.y() + st::notificationsSampleTopSkip) : (screenRect.y() + screenRect.height() - st::notificationsSampleBottomSkip - st::notificationSampleSize.height()); + if (corner == static_cast(_chosenCorner)) { + auto count = currentCount(); + for (int i = 0; i != kMaxNotificationsCount; ++i) { + auto opacity = _sampleOpacities[i].current(getms(), (i < count) ? 1. : 0.); + p.setOpacity(opacity); + p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); + sampleTop += (isTop ? 1 : -1) * (st::notificationSampleSize.height() + st::notificationsSampleMargin); + } + } else { + p.setOpacity(st::notificationSampleOpacity); + p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); + p.setOpacity(1.); + } + } + auto labelTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop; p.drawTextLeft(contentLeft, labelTop, width(), lang(lng_settings_notifications_count)); } +void NotificationsBox::countChanged() { + auto count = currentCount(); + auto moreSamples = (count > _oldCount); + auto from = moreSamples ? 0. : 1.; + auto to = moreSamples ? 1. : 0.; + auto indexDelta = moreSamples ? 1 : -1; + auto animatedDelta = moreSamples ? 0 : -1; + for (; _oldCount != count; _oldCount += indexDelta) { + _sampleOpacities[_oldCount + animatedDelta].start([this] { update(); }, from, to, st::notifyFastAnim); + } + + if (currentCount() != Global::NotificationsCount()) { + Global::SetNotificationsCount(currentCount()); + Global::RefNotifySettingsChanged().notify(Notify::ChangeType::MaxCount); + Local::writeUserSettings(); + } +} + int NotificationsBox::getContentLeft() const { return (width() - st::notificationsBoxMonitor.width()) / 2; } @@ -84,8 +188,7 @@ QRect NotificationsBox::getScreenRect() const { } void NotificationsBox::resizeEvent(QResizeEvent *e) { - _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); - _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); + _done->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _done->height()); auto screenRect = getScreenRect(); auto sliderTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop + st::notificationsBoxCountTop; @@ -95,14 +198,201 @@ void NotificationsBox::resizeEvent(QResizeEvent *e) { AbstractBox::resizeEvent(e); } -void NotificationsBox::mousePressEvent(QMouseEvent *e) { +void NotificationsBox::prepareNotificationSampleSmall() { + auto width = st::notificationSampleSize.width(); + auto height = st::notificationSampleSize.height(); + auto sampleImage = QImage(width * cIntRetinaFactor(), height * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + sampleImage.setDevicePixelRatio(cRetinaFactor()); + sampleImage.fill(st::notifyBg->c); + { + Painter p(&sampleImage); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::HighQualityAntialiasing); + auto padding = height / 8; + auto userpicSize = height - 2 * padding; + p.setBrush(st::notificationSampleUserpicFg); + p.drawEllipse(rtlrect(padding, padding, userpicSize, userpicSize, width)); + + auto rowLeft = height; + auto rowHeight = padding; + auto nameTop = (height - 5 * padding) / 2; + auto nameWidth = height; + p.setBrush(st::notificationSampleNameFg); + p.drawRoundedRect(rtlrect(rowLeft, nameTop, nameWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); + + auto rowWidth = (width - rowLeft - 3 * padding); + auto rowTop = nameTop + rowHeight + padding; + p.setBrush(st::notificationSampleTextFg); + p.drawRoundedRect(rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); + rowTop += rowHeight + padding; + p.drawRoundedRect(rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2); + + auto closeLeft = width - 2 * padding; + p.fillRect(rtlrect(closeLeft, padding, padding, padding, width), st::notificationSampleCloseFg); + } + _notificationSampleSmall = App::pixmapFromImageInPlace(std_::move(sampleImage)); + _notificationSampleSmall.setDevicePixelRatio(cRetinaFactor()); +} + +void NotificationsBox::prepareNotificationSampleLarge() { + int w = st::notifyWidth, h = st::notifyMinHeight; + auto sampleImage = QImage(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + sampleImage.setDevicePixelRatio(cRetinaFactor()); + sampleImage.fill(st::notifyBg->c); + { + Painter p(&sampleImage); + p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); + p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); + p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); + p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b); + + static QPixmap icon = App::pixmapFromImageInPlace(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), icon); + + int itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width; + + auto rectForName = rtlrect(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height, w); + + static QString notifyText = st::dialogsTextFont->elided(lang(lng_notification_preview), itemWidth); + p.setFont(st::dialogsTextFont); + p.setPen(st::dialogsTextFgService); + p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText); + + p.setPen(st::dialogsNameFg); + p.setFont(st::msgNameFont); + + static QString notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); + p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); + } + + _notificationSampleLarge = App::pixmapFromImageInPlace(std_::move(sampleImage)); +} + +void NotificationsBox::removeSample(SampleWidget *widget) { + for (auto &samples : _cornerSamples) { + for (int i = 0, size = samples.size(); i != size; ++i) { + if (samples[i] == widget) { + for (int j = i + 1; j != size; ++j) { + samples[j]->detach(); + } + samples.resize(i); + break; + } + } + } } void NotificationsBox::mouseMoveEvent(QMouseEvent *e) { + auto screenRect = getScreenRect(); + auto cornerWidth = screenRect.width() / 3; + auto cornerHeight = screenRect.height() / 3; + auto topLeft = rtlrect(screenRect.x(), screenRect.y(), cornerWidth, cornerHeight, width()); + auto topRight = rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y(), cornerWidth, cornerHeight, width()); + auto bottomRight = rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width()); + auto bottomLeft = rtlrect(screenRect.x(), screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width()); + if (topLeft.contains(e->pos())) { + setOverCorner(Notify::ScreenCorner::TopLeft); + } else if (topRight.contains(e->pos())) { + setOverCorner(Notify::ScreenCorner::TopRight); + } else if (bottomRight.contains(e->pos())) { + setOverCorner(Notify::ScreenCorner::BottomRight); + } else if (bottomLeft.contains(e->pos())) { + setOverCorner(Notify::ScreenCorner::BottomLeft); + } else { + clearOverCorner(); + } +} +void NotificationsBox::leaveEvent(QEvent *e) { + clearOverCorner(); +} + +void NotificationsBox::setOverCorner(Notify::ScreenCorner corner) { + if (_isOverCorner) { + if (corner == _overCorner) { + return; + } + for_const (auto widget, _cornerSamples[static_cast(_overCorner)]) { + widget->hideFast(); + } + } else { + _isOverCorner = true; + setCursor(style::cur_pointer); + Global::SetNotificationsDemoIsShown(true); + Global::RefNotifySettingsChanged().notify(Notify::ChangeType::DemoIsShown); + } + _overCorner = corner; + + auto &samples = _cornerSamples[static_cast(_overCorner)]; + auto samplesAlready = samples.size(); + auto samplesNeeded = currentCount(); + auto samplesLeave = qMin(samplesAlready, samplesNeeded); + for (int i = 0; i != samplesLeave; ++i) { + samples[i]->showFast(); + } + if (samplesNeeded > samplesLeave) { + auto r = psDesktopRect(); + auto isLeft = Notify::IsLeftCorner(_overCorner); + auto isTop = Notify::IsTopCorner(_overCorner); + auto sampleLeft = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX); + auto sampleTop = isTop ? (r.y() + st::notifyDeltaY) : (r.y() + r.height() - st::notifyDeltaY - st::notifyMinHeight); + for (int i = samplesLeave; i != samplesNeeded; ++i) { + auto widget = std_::make_unique(this, _notificationSampleLarge); + widget->move(sampleLeft, sampleTop + (isTop ? 1 : -1) * i * (st::notifyMinHeight + st::notifyDeltaY)); + widget->showFast(); + samples.push_back(widget.release()); + } + } else { + for (int i = samplesLeave; i != samplesAlready; ++i) { + samples[i]->hideFast(); + } + } +} + +void NotificationsBox::clearOverCorner() { + if (_isOverCorner) { + _isOverCorner = false; + setCursor(style::cur_default); + Global::SetNotificationsDemoIsShown(false); + Global::RefNotifySettingsChanged().notify(Notify::ChangeType::DemoIsShown); + + for_const (auto &samples, _cornerSamples) { + for_const (auto widget, samples) { + widget->hideFast(); + } + } + } +} + +int NotificationsBox::currentCount() const { + return _countSlider->activeSection() + 1; +} + +void NotificationsBox::mousePressEvent(QMouseEvent *e) { + _isDownCorner = _isOverCorner; + _downCorner = _overCorner; } void NotificationsBox::mouseReleaseEvent(QMouseEvent *e) { + auto isDownCorner = createAndSwap(_isDownCorner); + if (isDownCorner && _isOverCorner && _downCorner == _overCorner && _downCorner != _chosenCorner) { + _chosenCorner = _downCorner; + update(); + if (_chosenCorner != Global::NotificationsCorner()) { + Global::SetNotificationsCorner(_chosenCorner); + Global::RefNotifySettingsChanged().notify(Notify::ChangeType::Corner); + Local::writeUserSettings(); + } + } +} + +NotificationsBox::~NotificationsBox() { + for_const (auto &samples, _cornerSamples) { + for_const (auto widget, samples) { + widget->detach(); + } + } + clearOverCorner(); } diff --git a/Telegram/SourceFiles/boxes/notifications_box.h b/Telegram/SourceFiles/boxes/notifications_box.h index 14cc0ab347..9e978f4f7f 100644 --- a/Telegram/SourceFiles/boxes/notifications_box.h +++ b/Telegram/SourceFiles/boxes/notifications_box.h @@ -30,23 +30,48 @@ class DiscreteSlider; } // namespace Ui class NotificationsBox : public AbstractBox { - Q_OBJECT - public: NotificationsBox(); + ~NotificationsBox(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: + using ScreenCorner = Notify::ScreenCorner; + void countChanged(); + void setOverCorner(ScreenCorner corner); + void clearOverCorner(); + + class SampleWidget; + void removeSample(SampleWidget *widget); + + int currentCount() const; + QRect getScreenRect() const; int getContentLeft() const; + void prepareNotificationSampleSmall(); + void prepareNotificationSampleLarge(); + QPixmap _notificationSampleSmall; + QPixmap _notificationSampleLarge; + ScreenCorner _chosenCorner; + std_::vector_of_moveable _sampleOpacities; + + bool _isOverCorner = false; + ScreenCorner _overCorner = ScreenCorner::TopLeft; + bool _isDownCorner = false; + ScreenCorner _downCorner = ScreenCorner::TopLeft; + + int _oldCount; ChildWidget _countSlider; - ChildWidget _save, _cancel; + ChildWidget _done; + + QVector _cornerSamples[4]; }; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3ebe4eec01..6071638e8c 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -635,6 +635,9 @@ struct Data { bool IncludeMuted = true; DBINotifyView NotifyView = dbinvShowPreview; bool NativeNotifications = false; + int NotificationsCount = 3; + Notify::ScreenCorner NotificationsCorner = Notify::ScreenCorner::BottomRight; + bool NotificationsDemoIsShown = false; base::Observable NotifySettingsChanged; DBIConnectionType ConnectionType = dbictAuto; @@ -742,6 +745,9 @@ DefineVar(Global, bool, RestoreSoundNotifyFromTray); DefineVar(Global, bool, IncludeMuted); DefineVar(Global, DBINotifyView, NotifyView); DefineVar(Global, bool, NativeNotifications); +DefineVar(Global, int, NotificationsCount); +DefineVar(Global, Notify::ScreenCorner, NotificationsCorner); +DefineVar(Global, bool, NotificationsDemoIsShown); DefineRefVar(Global, base::Observable, NotifySettingsChanged); DefineVar(Global, DBIConnectionType, ConnectionType); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 4d2577e520..c0d6c6d150 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -149,8 +149,26 @@ enum class ChangeType { IncludeMuted, DesktopEnabled, ViewParams, + MaxCount, + Corner, + DemoIsShown, }; +enum class ScreenCorner { + TopLeft = 0, + TopRight = 1, + BottomRight = 2, + BottomLeft = 3, +}; + +inline bool IsLeftCorner(ScreenCorner corner) { + return (corner == ScreenCorner::TopLeft) || (corner == ScreenCorner::BottomLeft); +} + +inline bool IsTopCorner(ScreenCorner corner) { + return (corner == ScreenCorner::TopLeft) || (corner == ScreenCorner::TopRight); +} + } // namespace Notify #define DeclareReadOnlyVar(Type, Name) const Type &Name(); @@ -308,6 +326,9 @@ DeclareVar(bool, RestoreSoundNotifyFromTray); DeclareVar(bool, IncludeMuted); DeclareVar(DBINotifyView, NotifyView); DeclareVar(bool, NativeNotifications); +DeclareVar(int, NotificationsCount); +DeclareVar(Notify::ScreenCorner, NotificationsCorner); +DeclareVar(bool, NotificationsDemoIsShown); DeclareRefVar(base::Observable, NotifySettingsChanged); DeclareVar(DBIConnectionType, ConnectionType); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index e8c6cc4481..9f43d60f49 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -549,6 +549,8 @@ enum { dbiVideoVolume = 0x42, dbiStickersRecentLimit = 0x43, dbiNativeNotifications = 0x44, + dbiNotificationsCount = 0x45, + dbiNotificationsCorner = 0x46, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1004,6 +1006,22 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version) { Global::SetNativeNotifications(v == 1); } break; + case dbiNotificationsCount: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetNotificationsCount((v > 0 ? v : 3)); + } break; + + case dbiNotificationsCorner: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetNotificationsCorner(static_cast((v >= 0 && v < 4) ? v : 2)); + } break; + case dbiWorkMode: { qint32 v; stream >> v; @@ -1572,7 +1590,7 @@ void _writeUserSettings() { _writeMap(WriteMapFast); } - uint32 size = 18 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 20 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); @@ -1597,6 +1615,8 @@ void _writeUserSettings() { data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); data.stream << quint32(dbiNotifyView) << qint32(Global::NotifyView()); data.stream << quint32(dbiNativeNotifications) << qint32(Global::NativeNotifications()); + data.stream << quint32(dbiNotificationsCount) << qint32(Global::NotificationsCount()); + data.stream << quint32(dbiNotificationsCorner) << qint32(Global::NotificationsCorner()); data.stream << quint32(dbiAskDownloadPath) << qint32(Global::AskDownloadPath()); data.stream << quint32(dbiDownloadPath) << (Global::AskDownloadPath() ? QString() : Global::DownloadPath()) << (Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); diff --git a/Telegram/SourceFiles/settings/settings_scale_widget.cpp b/Telegram/SourceFiles/settings/settings_scale_widget.cpp index 82237a48d7..6fb6a3859a 100644 --- a/Telegram/SourceFiles/settings/settings_scale_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_scale_widget.cpp @@ -59,8 +59,7 @@ void ScaleWidget::createControls() { _scale->addSection(scaleLabel(dbisOneAndHalf)); _scale->addSection(scaleLabel(dbisTwo)); _scale->setActiveSectionFast(cEvalScale(cConfigScale()) - 1); - - connect(_scale, SIGNAL(sectionActivated()), this, SLOT(onSectionActivated())); + _scale->setSectionActivatedCallback([this] { scaleChanged(); }); } void ScaleWidget::onAutoChosen() { @@ -102,7 +101,7 @@ void ScaleWidget::setScale(DBIScale newScale) { } } -void ScaleWidget::onSectionActivated() { +void ScaleWidget::scaleChanged() { auto newScale = dbisAuto; switch (_scale->activeSection()) { case 0: newScale = dbisOne; break; diff --git a/Telegram/SourceFiles/settings/settings_scale_widget.h b/Telegram/SourceFiles/settings/settings_scale_widget.h index d518b253c1..39684d7549 100644 --- a/Telegram/SourceFiles/settings/settings_scale_widget.h +++ b/Telegram/SourceFiles/settings/settings_scale_widget.h @@ -38,10 +38,10 @@ public: private slots: void onAutoChosen(); - void onSectionActivated(); void onRestartNow(); private: + void scaleChanged(); void createControls(); void setScale(DBIScale newScale); diff --git a/Telegram/SourceFiles/ui/widgets/discrete_slider.cpp b/Telegram/SourceFiles/ui/widgets/discrete_slider.cpp index 65486067bb..b572e5c128 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/discrete_slider.cpp @@ -30,11 +30,17 @@ DiscreteSlider::DiscreteSlider(QWidget *parent) : TWidget(parent) setCursor(style::cur_pointer); } +void DiscreteSlider::setSectionActivatedCallback(SectionActivatedCallback &&callback) { + _callback = std_::move(callback); +} + void DiscreteSlider::setActiveSection(int index) { setSelectedSection(index); if (_activeIndex != index) { _activeIndex = index; - emit sectionActivated(); + if (_callback) { + _callback(); + } } } diff --git a/Telegram/SourceFiles/ui/widgets/discrete_slider.h b/Telegram/SourceFiles/ui/widgets/discrete_slider.h index 3235e00ece..51c3bc1ff6 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_slider.h +++ b/Telegram/SourceFiles/ui/widgets/discrete_slider.h @@ -23,17 +23,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class DiscreteSlider : public TWidget { - Q_OBJECT - public: DiscreteSlider(QWidget *parent); + void addSection(const QString &label); + int activeSection() const { return _activeIndex; } void setActiveSection(int index); void setActiveSectionFast(int index); - void addSection(const QString &label); + + using SectionActivatedCallback = base::lambda_unique; + void setSectionActivatedCallback(SectionActivatedCallback &&callback); protected: void paintEvent(QPaintEvent *e) override; @@ -43,9 +45,6 @@ protected: int resizeGetHeight(int newWidth) override; -signals: - void sectionActivated(); - private: void resizeSections(int newWidth); int getIndexFromPosition(QPoint pos); @@ -62,6 +61,8 @@ private: QList
_sections; int _activeIndex = 0; + SectionActivatedCallback _callback; + bool _pressed = false; int _selected = 0; anim::ivalue a_left = { 0 }; diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index e63719950b..8ac18e1a01 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -34,25 +34,26 @@ namespace Notifications { namespace Default { namespace { -// 3 desktop notifies at the same time. -constexpr int kNotifyWindowsCount = 3; - NeverFreedPointer ManagerInstance; -int notificationWidth() { - static auto result = ([] { - auto replyWidth = st::defaultBoxButton.font->width(lang(lng_notification_reply).toUpper()) - st::defaultBoxButton.width; - auto textLeft = st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft; - auto minWidth = textLeft + replyWidth + st::boxButtonPadding.right(); - return qMax(st::notifyMinWidth, minWidth); - })(); - return result; -} - int notificationMaxHeight() { return st::notifyMinHeight + st::notifyReplyArea.heightMax + st::notifyBorderWidth; } +QPoint notificationStartPosition() { + auto r = psDesktopRect(); + auto isLeft = Notify::IsLeftCorner(Global::NotificationsCorner()); + auto isTop = Notify::IsTopCorner(Global::NotificationsCorner()); + auto x = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX); + auto y = isTop ? r.y() : (r.y() + r.height()); + return QPoint(x, y); +} + +internal::Widget::Direction notificationShiftDirection() { + auto isTop = Notify::IsTopCorner(Global::NotificationsCorner()); + return isTop ? internal::Widget::Direction::Down : internal::Widget::Direction::Up; +} + } // namespace void start() { @@ -73,13 +74,69 @@ Manager::Manager() { notification->updatePeerPhoto(); } }); + subscribe(Global::RefNotifySettingsChanged(), [this](const Notify::ChangeType &change) { + settingsChanged(change); + }); _inputCheckTimer.setTimeoutHandler([this] { checkLastInput(); }); } +bool Manager::hasReplyingNotification() const { + for_const (auto notification, _notifications) { + if (notification->isReplying()) { + return true; + } + } + return false; +} + +void Manager::settingsChanged(const Notify::ChangeType &change) { + if (change == Notify::ChangeType::Corner) { + auto startPosition = notificationStartPosition(); + auto shiftDirection = notificationShiftDirection(); + for_const (auto notification, _notifications) { + notification->updatePosition(startPosition, shiftDirection); + } + if (_hideAll) { + _hideAll->updatePosition(startPosition, shiftDirection); + } + } else if (change == Notify::ChangeType::MaxCount) { + int allow = Global::NotificationsCount(); + for (int i = _notifications.size(); i != 0;) { + auto notification = _notifications[--i]; + if (notification->isUnlinked()) continue; + if (--allow < 0) { + notification->unlinkHistory(); + } + } + if (allow > 0) { + for (int i = 0; i != allow; ++i) { + showNextFromQueue(); + } + } + } else if (change == Notify::ChangeType::DemoIsShown) { + auto demoIsShown = Global::NotificationsDemoIsShown(); + _demoMasterOpacity.start([this] { demoMasterOpacityCallback(); }, demoIsShown ? 1. : 0., demoIsShown ? 0. : 1., st::notifyFastAnim); + } +} + +void Manager::demoMasterOpacityCallback() { + for_const (auto notification, _notifications) { + notification->updateOpacity(); + } + if (_hideAll) { + _hideAll->updateOpacity(); + } +} + +float64 Manager::demoMasterOpacity() const { + return _demoMasterOpacity.current(Global::NotificationsDemoIsShown() ? 0. : 1.); +} + void Manager::checkLastInput() { + auto replying = hasReplyingNotification(); auto waiting = false; for_const (auto notification, _notifications) { - if (!notification->checkLastInput()) { + if (!notification->checkLastInput(replying)) { waiting = true; } } @@ -89,14 +146,7 @@ void Manager::checkLastInput() { } void Manager::startAllHiding() { - auto hasReplyingNotification = false; - for_const (auto notification, _notifications) { - if (notification->isReplying()) { - hasReplyingNotification = true; - break; - } - } - if (!hasReplyingNotification) { + if (!hasReplyingNotification()) { int notHidingCount = 0; for_const (auto notification, _notifications) { if (notification->isShowing()) { @@ -123,13 +173,15 @@ void Manager::stopAllHiding() { void Manager::showNextFromQueue() { if (!_queuedNotifications.isEmpty()) { - int count = kNotifyWindowsCount; + int count = Global::NotificationsCount(); for_const (auto notification, _notifications) { if (notification->isUnlinked()) continue; --count; } if (count > 0) { - auto position = notificationStartPosition(); + auto startPosition = notificationStartPosition(); + auto startShift = 0; + auto shiftDirection = notificationShiftDirection(); do { auto queued = _queuedNotifications.front(); _queuedNotifications.pop_front(); @@ -140,7 +192,7 @@ void Manager::showNextFromQueue() { queued.author, queued.item, queued.forwardedCount, - position); + startPosition, startShift, shiftDirection); Platform::Notifications::defaultNotificationShown(notification.get()); _notifications.push_back(notification.release()); --count; @@ -155,27 +207,18 @@ void Manager::showNextFromQueue() { } } -QPoint Manager::notificationStartPosition() const { - auto r = psDesktopRect(); - auto x = r.x() + r.width() - notificationWidth() - st::notifyDeltaX; - auto y = r.y() + r.height(); - return QPoint(x, y); -} - void Manager::moveWidgets() { - auto startPosition = notificationStartPosition(); - auto top = startPosition.y(); - int firstLeft = 0, firstTopCurrent = 0, firstTop = 0, count = 0; + auto shift = st::notifyDeltaY; + int lastShift = 0, lastShiftCurrent = 0, count = 0; for (int i = _notifications.size(); i != 0;) { auto notification = _notifications[--i]; if (notification->isUnlinked()) continue; - top -= notification->height() + st::notifyDeltaY; - notification->moveTop(top); + notification->changeShift(shift); + shift += notification->height() + st::notifyDeltaY; - firstLeft = notification->x(); - firstTopCurrent = notification->y(); - firstTop = top; + lastShiftCurrent = notification->currentShift(); + lastShift = shift; ++count; } @@ -183,9 +226,9 @@ void Manager::moveWidgets() { if (count > 1 || !_queuedNotifications.isEmpty()) { auto deltaY = st::notifyHideAll.height + st::notifyDeltaY; if (!_hideAll) { - _hideAll = new HideAllButton(QPoint(firstLeft, firstTopCurrent - deltaY)); + _hideAll = new HideAllButton(notificationStartPosition(), lastShiftCurrent, notificationShiftDirection()); } - _hideAll->moveTop(firstTop - deltaY); + _hideAll->changeShift(lastShift); _hideAll->stopHiding(); } else if (_hideAll) { _hideAll->startHidingFast(); @@ -196,18 +239,18 @@ void Manager::changeNotificationHeight(Notification *notification, int newHeight auto deltaHeight = newHeight - notification->height(); if (!deltaHeight) return; - notification->addToHeight(deltaHeight, Notification::AddToHeight::Above); + notification->addToHeight(deltaHeight); auto index = _notifications.indexOf(notification); if (index > 0) { for (int i = 0; i != index; ++i) { auto notification = _notifications[i]; if (notification->isUnlinked()) continue; - notification->addToTop(-deltaHeight); + notification->addToShift(deltaHeight); } } if (_hideAll) { - _hideAll->addToTop(-deltaHeight); + _hideAll->addToShift(deltaHeight); } } @@ -293,57 +336,56 @@ Manager::~Manager() { namespace internal { -Widget::Widget(QPoint position) : TWidget(nullptr) +Widget::Widget(QPoint startPosition, int shift, Direction shiftDirection) : TWidget(nullptr) , _opacityDuration(st::notifyFastAnim) , a_opacity(0, 1) , a_func(anim::linear) -, _a_appearance(animation(this, &Widget::step_appearance)) -, a_top(position.y()) -, _a_movement(animation(this, &Notification::step_movement)) { +, _a_opacity(animation(this, &Widget::step_opacity)) +, _startPosition(startPosition) +, _direction(shiftDirection) +, a_shift(shift) +, _a_shift(animation(this, &Widget::step_shift)) { setWindowOpacity(0.); setAttribute(Qt::WA_OpaquePaintEvent); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint); setAttribute(Qt::WA_MacAlwaysShowToolWindow); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_TranslucentBackground, true); - _a_appearance.start(); + _a_opacity.start(); } -void Widget::step_appearance(float64 ms, bool timer) { +void Widget::step_opacity(float64 ms, bool timer) { float64 dt = ms / float64(_opacityDuration); if (dt >= 1) { a_opacity.finish(); - _a_appearance.stop(); + _a_opacity.stop(); if (_hiding) { deleteLater(); } } else { a_opacity.update(dt, a_func); } - setWindowOpacity(a_opacity.current()); + updateOpacity(); update(); } -void Widget::step_movement(float64 ms, bool timer) { +void Widget::step_shift(float64 ms, bool timer) { float64 dt = ms / float64(st::notifyFastAnim); if (dt >= 1) { - a_top.finish(); + a_shift.finish(); } else { - a_top.update(dt, anim::linear); + a_shift.update(dt, anim::linear); } - move(x(), a_top.current()); - update(); + moveByShift(); } void Widget::hideSlow() { - animHide(st::notifySlowHide, st::notifySlowHideFunc); + hideAnimated(st::notifySlowHide, st::notifySlowHideFunc); } void Widget::hideFast() { - animHide(st::notifyFastAnim, anim::linear); + hideAnimated(st::notifyFastAnim, anim::linear); } void Widget::hideStop() { @@ -352,29 +394,39 @@ void Widget::hideStop() { a_func = anim::linear; a_opacity.start(1); _hiding = false; - _a_appearance.start(); + _a_opacity.start(); } } -void Widget::animHide(float64 duration, anim::transition func) { +void Widget::hideAnimated(float64 duration, anim::transition func) { _opacityDuration = duration; a_func = func; a_opacity.start(0); _hiding = true; - _a_appearance.start(); + _a_opacity.start(); } -void Widget::moveTop(int top) { - a_top.start(top); - _a_movement.start(); -} - -void Widget::addToHeight(int add, AddToHeight aboveOrBelow) { - int newHeight = height() + add; - if (aboveOrBelow == AddToHeight::Above) { - a_top.add(-add); +void Widget::updateOpacity() { + if (auto manager = ManagerInstance.data()) { + setWindowOpacity(a_opacity.current() * manager->demoMasterOpacity()); } - updateGeometry(x(), a_top.current(), width(), newHeight); +} + +void Widget::changeShift(int top) { + a_shift.start(top); + _a_shift.start(); +} + +void Widget::updatePosition(QPoint startPosition, Direction shiftDirection) { + _startPosition = startPosition; + _direction = shiftDirection; + moveByShift(); +} + +void Widget::addToHeight(int add) { + auto newHeight = height() + add; + auto newPosition = computePosition(newHeight); + updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight); } void Widget::updateGeometry(int x, int y, int width, int height) { @@ -382,9 +434,21 @@ void Widget::updateGeometry(int x, int y, int width, int height) { update(); } -void Widget::addToTop(int add) { - a_top.add(add); - move(x(), a_top.current()); +void Widget::addToShift(int add) { + a_shift.add(add); + moveByShift(); +} + +void Widget::moveByShift() { + move(computePosition(height())); +} + +QPoint Widget::computePosition(int height) const { + auto realShift = a_shift.current(); + if (_direction == Direction::Up) { + realShift = -realShift - height; + } + return QPoint(_startPosition.x(), _startPosition.y() + realShift); } Background::Background(QWidget *parent) : TWidget(parent) { @@ -400,7 +464,7 @@ void Background::paintEvent(QPaintEvent *e) { p.fillRect(st::notifyBorderWidth, height() - st::notifyBorderWidth, width() - 2 * st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder); } -Notification::Notification(History *history, PeerData *peer, PeerData *author, HistoryItem *msg, int forwardedCount, QPoint position) : Widget(position) +Notification::Notification(History *history, PeerData *peer, PeerData *author, HistoryItem *msg, int forwardedCount, QPoint startPosition, int shift, Direction shiftDirection) : Widget(startPosition, shift, shiftDirection) , _history(history) , _peer(peer) , _author(author) @@ -411,7 +475,8 @@ Notification::Notification(History *history, PeerData *peer, PeerData *author, H #endif // Q_OS_WIN && !Q_OS_WINRT , _close(this, st::notifyClose) , _reply(this, lang(lng_notification_reply), st::defaultBoxButton) { - updateGeometry(position.x(), position.y(), notificationWidth(), st::notifyMinHeight); + auto position = computePosition(st::notifyMinHeight); + updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyMinHeight); _userpicLoaded = _peer ? _peer->userpicLoaded() : true; updateNotifyDisplay(); @@ -460,7 +525,7 @@ void Notification::prepareActionsCache() { _buttonsCache = App::pixmapFromImageInPlace(std_::move(actionsCacheImg)); } -bool Notification::checkLastInput() { +bool Notification::checkLastInput(bool hasReplyingNotifications) { if (!_waitingForInput) return true; auto wasUserInput = true; // TODO @@ -472,7 +537,7 @@ bool Notification::checkLastInput() { #endif // Q_OS_WIN && !Q_OS_WINRT if (wasUserInput) { _waitingForInput = false; - if (!isReplying()) { + if (!hasReplyingNotifications) { _hideTimer.start(st::notifyWaitLongHide); } return true; @@ -684,9 +749,6 @@ void Notification::changeHeight(int newHeight) { bool Notification::unlinkHistory(History *history) { auto unlink = _history && (history == _history || !history); if (unlink) { - if (_history->peer->id != 4456802837) { - int a = 0; - } hideFast(); _history = nullptr; _item = nullptr; @@ -748,10 +810,11 @@ Notification::~Notification() { } } -HideAllButton::HideAllButton(QPoint position) : Widget(position) { +HideAllButton::HideAllButton(QPoint startPosition, int shift, Direction shiftDirection) : Widget(startPosition, shift, shiftDirection) { setCursor(style::cur_pointer); - updateGeometry(position.x(), position.y(), notificationWidth(), st::notifyHideAll.height); + auto position = computePosition(st::notifyHideAll.height); + updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAll.height); hide(); createWinId(); diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h index 6094b8037e..10886ee8cf 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.h +++ b/Telegram/SourceFiles/window/notifications_manager_default.h @@ -31,6 +31,7 @@ namespace Window { namespace Notifications { namespace Default { namespace internal { +class Widget; class Notification; class HideAllButton; } // namespace internal @@ -59,6 +60,7 @@ private: friend class Notification; using HideAllButton = internal::HideAllButton; friend class HideAllButton; + friend class internal::Widget; void doUpdateAll() override; void doShowNotification(HistoryItem *item, int forwardedCount) override; @@ -75,9 +77,14 @@ private: void stopAllHiding(); void checkLastInput(); - QPoint notificationStartPosition() const; + float64 demoMasterOpacity() const; + void demoMasterOpacityCallback(); + void moveWidgets(); void changeNotificationHeight(Notification *widget, int newHeight); + void settingsChanged(const Notify::ChangeType &change); + + bool hasReplyingNotification() const; using Notifications = QList; Notifications _notifications; @@ -105,46 +112,57 @@ private: using QueuedNotifications = QList; QueuedNotifications _queuedNotifications; + FloatAnimation _demoMasterOpacity; + }; namespace internal { class Widget : public TWidget { public: - Widget(QPoint position); + enum class Direction { + Up, + Down, + }; + Widget(QPoint startPosition, int shift, Direction shiftDirection); bool isShowing() const { - return _a_appearance.animating() && !_hiding; + return _a_opacity.animating() && !_hiding; } - void moveTop(int top); - enum class AddToHeight { - Above, - Below, - }; - void addToHeight(int add, AddToHeight aboveOrBelow); - void addToTop(int add); + void updateOpacity(); + void changeShift(int top); + int currentShift() const { + return a_shift.current(); + } + void updatePosition(QPoint startPosition, Direction shiftDirection); + void addToHeight(int add); + void addToShift(int add); protected: void hideSlow(); void hideFast(); void hideStop(); + QPoint computePosition(int height) const; virtual void updateGeometry(int x, int y, int width, int height); private: - void animHide(float64 duration, anim::transition func); - void step_appearance(float64 ms, bool timer); - void step_movement(float64 ms, bool timer); + void moveByShift(); + void hideAnimated(float64 duration, anim::transition func); + void step_opacity(float64 ms, bool timer); + void step_shift(float64 ms, bool timer); bool _hiding = false; float64 _opacityDuration; anim::fvalue a_opacity; anim::transition a_func; - Animation _a_appearance; + Animation _a_opacity; - anim::ivalue a_top; - Animation _a_movement; + QPoint _startPosition; + Direction _direction; + anim::ivalue a_shift; + Animation _a_shift; }; @@ -161,7 +179,7 @@ class Notification : public Widget { Q_OBJECT public: - Notification(History *history, PeerData *peer, PeerData *author, HistoryItem *item, int forwardedCount, QPoint position); + Notification(History *history, PeerData *peer, PeerData *author, HistoryItem *item, int forwardedCount, QPoint startPosition, int shift, Direction shiftDirection); void startHiding(); void stopHiding(); @@ -179,7 +197,7 @@ public: // Called only by Manager. void itemRemoved(HistoryItem *del); bool unlinkHistory(History *history = nullptr); - bool checkLastInput(); + bool checkLastInput(bool hasReplyingNotifications); ~Notification(); @@ -237,7 +255,7 @@ private: class HideAllButton : public Widget { public: - HideAllButton(QPoint position); + HideAllButton(QPoint startPosition, int shift, Direction shiftDirection); void startHiding(); void startHidingFast(); diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index c2b4a20d24..721d756c99 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -37,7 +37,7 @@ notifyTextTop: 7px; notifySlowHideFunc: transition(easeInCirc); notifyWaitLongHide: 3000; notifyFastAnim: 150; -notifyMinWidth: 316px; +notifyWidth: 320px; notifyMinHeight: 80px; notifyDeltaX: 6px; notifyDeltaY: 7px;