diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f7e6ec51d..196b30b24 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -278,7 +278,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_show_preview" = "Show message preview"; "lng_settings_use_windows" = "Use Windows notifications"; "lng_settings_use_native_notifications" = "Use native notifications"; -"lng_settings_advanced_notifications" = "Notifications position and count"; "lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_count" = "Notifications count"; "lng_settings_sound_notify" = "Play sound"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index c885d0819..e98d5aa91 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -403,13 +403,9 @@ shareColumnSkip: 6px; shareActivateDuration: 150; shareScrollDuration: 300; -notificationsBoxHeight: 420px; -notificationsBoxMonitorTop: 63px; notificationsBoxMonitor: icon {{ "monitor", notificationsBoxMonitorFg }}; notificationsBoxScreenTop: 10px; notificationsBoxScreenSize: size(280px, 160px); -notificationsBoxCountLabelTop: 80px; -notificationsBoxCountTop: 30px; notificationsSampleSkip: 5px; notificationsSampleTopSkip: 5px; diff --git a/Telegram/SourceFiles/boxes/notifications_box.cpp b/Telegram/SourceFiles/boxes/notifications_box.cpp deleted file mode 100644 index 9685673a2..000000000 --- a/Telegram/SourceFiles/boxes/notifications_box.cpp +++ /dev/null @@ -1,420 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "boxes/notifications_box.h" - -#include "lang/lang_keys.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/discrete_sliders.h" -#include "styles/style_boxes.h" -#include "styles/style_dialogs.h" -#include "styles/style_window.h" -#include "messenger.h" -#include "storage/localstorage.h" -#include "auth_session.h" -#include "window/notifications_manager.h" -#include "platform/platform_specific.h" - -namespace { - -constexpr int kMaxNotificationsCount = 5; - -using ChangeType = Window::Notifications::ChangeType; - -} // 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()); - - setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::WindowStaysOnTopHint | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::Tool); - setAttribute(Qt::WA_MacAlwaysShowToolWindow); - setAttribute(Qt::WA_TransparentForMouseEvents); - setAttribute(Qt::WA_OpaquePaintEvent); - - 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(); - destroyDelayed(); - } - } - - void destroyDelayed() { - if (_deleted) return; - _deleted = true; - - // Ubuntu has a lag if deleteLater() called immediately. -#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 - QTimer::singleShot(1000, [this] { delete this; }); -#else // Q_OS_LINUX32 || Q_OS_LINUX64 - deleteLater(); -#endif // Q_OS_LINUX32 || Q_OS_LINUX64 - } - - NotificationsBox *_owner; - QPixmap _cache; - Animation _opacity; - bool _hiding = false; - bool _deleted = false; - -}; - -NotificationsBox::NotificationsBox(QWidget *parent) -: _chosenCorner(Global::NotificationsCorner()) -, _oldCount(snap(Global::NotificationsCount(), 1, kMaxNotificationsCount)) -, _countSlider(this) { -} - -void NotificationsBox::prepare() { - addButton(langFactory(lng_close), [this] { closeBox(); }); - - _sampleOpacities.reserve(kMaxNotificationsCount); - for (int i = 0; i != kMaxNotificationsCount; ++i) { - _countSlider->addSection(QString::number(i + 1)); - _sampleOpacities.push_back(Animation()); - } - _countSlider->setActiveSectionFast(_oldCount - 1); - _countSlider->sectionActivated( - ) | rpl::start_with_next( - [this] { countChanged(); }, - lifetime()); - - setMouseTracking(true); - - prepareNotificationSampleSmall(); - prepareNotificationSampleLarge(); - - setDimensions(st::boxWideWidth, st::notificationsBoxHeight); -} - -void NotificationsBox::paintEvent(QPaintEvent *e) { - BoxContent::paintEvent(e); - - Painter p(this); - - auto contentLeft = getContentLeft(); - - p.setFont(st::boxTitleFont); - p.setPen(st::boxTitleFg); - p.drawTextLeft(contentLeft, st::boxTitlePosition.y(), width(), lang(lng_settings_notifications_position)); - - 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); - } - p.setOpacity(1.); - } else { - p.setOpacity(st::notificationSampleOpacity); - p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); - p.setOpacity(1.); - } - } - - auto labelTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop; - p.setFont(st::boxTitleFont); - p.setPen(st::boxTitleFg); - 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()); - Auth().notifications().settingsChanged().notify(ChangeType::MaxCount); - Local::writeUserSettings(); - } -} - -int NotificationsBox::getContentLeft() const { - return (width() - st::notificationsBoxMonitor.width()) / 2; -} - -QRect NotificationsBox::getScreenRect() const { - auto screenLeft = (width() - st::notificationsBoxScreenSize.width()) / 2; - auto screenTop = st::notificationsBoxMonitorTop + st::notificationsBoxScreenTop; - return QRect(screenLeft, screenTop, st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height()); -} - -void NotificationsBox::resizeEvent(QResizeEvent *e) { - BoxContent::resizeEvent(e); - - auto screenRect = getScreenRect(); - auto sliderTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop + st::notificationsBoxCountTop; - auto contentLeft = getContentLeft(); - _countSlider->resizeToWidth(width() - 2 * contentLeft); - _countSlider->move(contentLeft, sliderTop); -} - -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::notificationBg->c); - { - Painter p(&sampleImage); - PainterHighQualityEnabler hq(p); - - p.setPen(Qt::NoPen); - - 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::prepareNotificationSampleUserpic() { - if (_notificationSampleUserpic.isNull()) { - _notificationSampleUserpic = App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin().scaled(st::notifyPhotoSize * cIntRetinaFactor(), st::notifyPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - _notificationSampleUserpic.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::notificationBg->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); - - prepareNotificationSampleUserpic(); - p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), _notificationSampleUserpic); - - 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); - - auto notifyText = st::dialogsTextFont->elided(lang(lng_notification_sample), 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); - - auto notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); - p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); - - st::notifyClose.icon.paint(p, w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPosition.x(), st::notifyClosePos.y() + st::notifyClose.iconPosition.y(), w); - } - - _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::leaveEventHook(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); - Auth().notifications().settingsChanged().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); - Auth().notifications().settingsChanged().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 = base::take(_isDownCorner); - if (isDownCorner && _isOverCorner && _downCorner == _overCorner && _downCorner != _chosenCorner) { - _chosenCorner = _downCorner; - update(); - - if (_chosenCorner != Global::NotificationsCorner()) { - Global::SetNotificationsCorner(_chosenCorner); - Auth().notifications().settingsChanged().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 deleted file mode 100644 index 4035989ac..000000000 --- a/Telegram/SourceFiles/boxes/notifications_box.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "boxes/abstract_box.h" - -namespace Ui { -class LinkButton; -class SettingsSlider; -} // namespace Ui - -class NotificationsBox : public BoxContent { -public: - NotificationsBox(QWidget*); - ~NotificationsBox(); - -protected: - void prepare() override; - - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void leaveEventHook(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(); - void prepareNotificationSampleUserpic(); - - QPixmap _notificationSampleUserpic; - QPixmap _notificationSampleSmall; - QPixmap _notificationSampleLarge; - ScreenCorner _chosenCorner; - std::vector _sampleOpacities; - - bool _isOverCorner = false; - ScreenCorner _overCorner = ScreenCorner::TopLeft; - bool _isDownCorner = false; - ScreenCorner _downCorner = ScreenCorner::TopLeft; - - int _oldCount; - object_ptr _countSlider; - - QVector _cornerSamples[4]; - -}; diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 9ece38f1c..641e48542 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -12,19 +12,505 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "lang/lang_keys.h" #include "info/profile/info_profile_button.h" #include "storage/localstorage.h" #include "window/notifications_manager.h" -#include "boxes/notifications_box.h" #include "platform/platform_notifications_manager.h" #include "mainwindow.h" +#include "messenger.h" #include "auth_session.h" #include "styles/style_settings.h" +#include "styles/style_boxes.h" +#include "styles/style_window.h" +#include "styles/style_dialogs.h" namespace Settings { namespace { +constexpr auto kMaxNotificationsCount = 5; + +int CurrentCount() { + return snap(Global::NotificationsCount(), 1, kMaxNotificationsCount); +} + +using ChangeType = Window::Notifications::ChangeType; + +class NotificationsCount : public Ui::RpWidget { +public: + NotificationsCount(QWidget *parent); + + void setCount(int count); + + ~NotificationsCount(); + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEventHook(QEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + + int resizeGetHeight(int newWidth) override; + +private: + using ScreenCorner = Notify::ScreenCorner; + void setOverCorner(ScreenCorner corner); + void clearOverCorner(); + + class SampleWidget; + void removeSample(SampleWidget *widget); + + QRect getScreenRect() const; + QRect getScreenRect(int width) const; + int getContentLeft() const; + void prepareNotificationSampleSmall(); + void prepareNotificationSampleLarge(); + void prepareNotificationSampleUserpic(); + + QPixmap _notificationSampleUserpic; + QPixmap _notificationSampleSmall; + QPixmap _notificationSampleLarge; + ScreenCorner _chosenCorner; + std::vector _sampleOpacities; + + bool _isOverCorner = false; + ScreenCorner _overCorner = ScreenCorner::TopLeft; + bool _isDownCorner = false; + ScreenCorner _downCorner = ScreenCorner::TopLeft; + + int _oldCount; + + QVector _cornerSamples[4]; + +}; + +class NotificationsCount::SampleWidget : public QWidget { +public: + SampleWidget(NotificationsCount *owner, const QPixmap &cache); + + void detach(); + void showFast(); + void hideFast(); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void startAnimation(); + void animationCallback(); + + void destroyDelayed(); + + NotificationsCount *_owner; + QPixmap _cache; + Animation _opacity; + bool _hiding = false; + bool _deleted = false; + +}; + +NotificationsCount::NotificationsCount(QWidget *parent) +: _chosenCorner(Global::NotificationsCorner()) +, _oldCount(CurrentCount()) { + setMouseTracking(true); + + _sampleOpacities.resize(kMaxNotificationsCount); + + prepareNotificationSampleSmall(); + prepareNotificationSampleLarge(); +} + +void NotificationsCount::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto contentLeft = getContentLeft(); + + auto screenRect = getScreenRect(); + p.fillRect( + screenRect.x(), + screenRect.y(), + st::notificationsBoxScreenSize.width(), + st::notificationsBoxScreenSize.height(), + st::notificationsBoxScreenBg); + + auto monitorTop = 0; + 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 = _oldCount; + 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); + } + p.setOpacity(1.); + } else { + p.setOpacity(st::notificationSampleOpacity); + p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall); + p.setOpacity(1.); + } + } +} + +void NotificationsCount::setCount(int count) { + 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 (count != Global::NotificationsCount()) { + Global::SetNotificationsCount(count); + Auth().notifications().settingsChanged().notify(ChangeType::MaxCount); + Local::writeUserSettings(); + } +} + +int NotificationsCount::getContentLeft() const { + return (width() - st::notificationsBoxMonitor.width()) / 2; +} + +QRect NotificationsCount::getScreenRect() const { + return getScreenRect(width()); +} + +QRect NotificationsCount::getScreenRect(int width) const { + auto screenLeft = (width - st::notificationsBoxScreenSize.width()) / 2; + auto screenTop = st::notificationsBoxScreenTop; + return QRect(screenLeft, screenTop, st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height()); +} + +int NotificationsCount::resizeGetHeight(int newWidth) { + update(); + return st::notificationsBoxMonitor.height(); +} + +void NotificationsCount::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::notificationBg->c); + { + Painter p(&sampleImage); + PainterHighQualityEnabler hq(p); + + p.setPen(Qt::NoPen); + + 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 NotificationsCount::prepareNotificationSampleUserpic() { + if (_notificationSampleUserpic.isNull()) { + _notificationSampleUserpic = App::pixmapFromImageInPlace( + Messenger::Instance().logoNoMargin().scaled( + st::notifyPhotoSize * cIntRetinaFactor(), + st::notifyPhotoSize * cIntRetinaFactor(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + _notificationSampleUserpic.setDevicePixelRatio(cRetinaFactor()); + } +} + +void NotificationsCount::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::notificationBg->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); + + prepareNotificationSampleUserpic(); + p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), _notificationSampleUserpic); + + 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); + + auto notifyText = st::dialogsTextFont->elided(lang(lng_notification_sample), 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); + + auto notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); + p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); + + st::notifyClose.icon.paint(p, w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPosition.x(), st::notifyClosePos.y() + st::notifyClose.iconPosition.y(), w); + } + + _notificationSampleLarge = App::pixmapFromImageInPlace(std::move(sampleImage)); +} + +void NotificationsCount::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 NotificationsCount::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 NotificationsCount::leaveEventHook(QEvent *e) { + clearOverCorner(); +} + +void NotificationsCount::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); + Auth().notifications().settingsChanged().notify(ChangeType::DemoIsShown); + } + _overCorner = corner; + + auto &samples = _cornerSamples[static_cast(_overCorner)]; + auto samplesAlready = samples.size(); + auto samplesNeeded = _oldCount; + 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 NotificationsCount::clearOverCorner() { + if (_isOverCorner) { + _isOverCorner = false; + setCursor(style::cur_default); + Global::SetNotificationsDemoIsShown(false); + Auth().notifications().settingsChanged().notify(ChangeType::DemoIsShown); + + for_const (auto &samples, _cornerSamples) { + for_const (auto widget, samples) { + widget->hideFast(); + } + } + } +} + +void NotificationsCount::mousePressEvent(QMouseEvent *e) { + _isDownCorner = _isOverCorner; + _downCorner = _overCorner; +} + +void NotificationsCount::mouseReleaseEvent(QMouseEvent *e) { + auto isDownCorner = base::take(_isDownCorner); + if (isDownCorner && _isOverCorner && _downCorner == _overCorner && _downCorner != _chosenCorner) { + _chosenCorner = _downCorner; + update(); + + if (_chosenCorner != Global::NotificationsCorner()) { + Global::SetNotificationsCorner(_chosenCorner); + Auth().notifications().settingsChanged().notify(ChangeType::Corner); + Local::writeUserSettings(); + } + } +} + +NotificationsCount::~NotificationsCount() { + for_const (auto &samples, _cornerSamples) { + for_const (auto widget, samples) { + widget->detach(); + } + } + clearOverCorner(); +} + +NotificationsCount::SampleWidget::SampleWidget( + NotificationsCount *owner, + const QPixmap &cache) +: QWidget(nullptr) +, _owner(owner) +, _cache(cache) { + resize( + cache.width() / cache.devicePixelRatio(), + cache.height() / cache.devicePixelRatio()); + + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) + | Qt::WindowStaysOnTopHint + | Qt::BypassWindowManagerHint + | Qt::NoDropShadowWindowHint + | Qt::Tool); + setAttribute(Qt::WA_MacAlwaysShowToolWindow); + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_OpaquePaintEvent); + + setWindowOpacity(0.); + show(); +} + +void NotificationsCount::SampleWidget::detach() { + _owner = nullptr; + hideFast(); +} + +void NotificationsCount::SampleWidget::showFast() { + _hiding = false; + startAnimation(); +} + +void NotificationsCount::SampleWidget::hideFast() { + _hiding = true; + startAnimation(); +} + +void NotificationsCount::SampleWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + p.drawPixmap(0, 0, _cache); +} + +void NotificationsCount::SampleWidget::startAnimation() { + _opacity.start( + [=] { animationCallback(); }, + _hiding ? 1. : 0., + _hiding ? 0. : 1., + st::notifyFastAnim); +} + +void NotificationsCount::SampleWidget::animationCallback() { + setWindowOpacity(_opacity.current(_hiding ? 0. : 1.)); + if (!_opacity.animating() && _hiding) { + if (_owner) { + _owner->removeSample(this); + } + hide(); + destroyDelayed(); + } +} + +void NotificationsCount::SampleWidget::destroyDelayed() { + if (_deleted) return; + _deleted = true; + + // Ubuntu has a lag if deleteLater() called immediately. +#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 + QTimer::singleShot(1000, [this] { delete this; }); +#else // Q_OS_LINUX32 || Q_OS_LINUX64 + deleteLater(); +#endif // Q_OS_LINUX32 || Q_OS_LINUX64 +} + +void SetupAdvancedNotifications(not_null container) { + AddSkip(container, st::settingsCheckboxesSkip); + AddDivider(container); + AddSkip(container, st::settingsCheckboxesSkip); + AddSubsectionTitle(container, lng_settings_notifications_position); + AddSkip(container, st::settingsCheckboxesSkip); + + const auto position = container->add( + object_ptr(container)); + + AddSkip(container, st::settingsCheckboxesSkip); + AddSubsectionTitle(container, lng_settings_notifications_count); + + const auto count = container->add( + object_ptr(container, st::settingsSlider), + st::settingsBigScalePadding); + for (int i = 0; i != kMaxNotificationsCount; ++i) { + count->addSection(QString::number(i + 1)); + } + count->setActiveSectionFast(CurrentCount() - 1); + count->sectionActivated( + ) | rpl::start_with_next([=](int section) { + position->setCount(section + 1); + }, count->lifetime()); + AddSkip(container, st::settingsCheckboxesSkip); +} + void SetupNotificationsContent(not_null container) { const auto checkbox = [&](LangKey label, bool checked) { return object_ptr( @@ -86,16 +572,8 @@ void SetupNotificationsContent(not_null container) { ? advancedSlide->entity() : nullptr; if (advancedWrap) { - AddSkip(advancedWrap, st::settingsCheckboxesSkip); - AddDivider(advancedWrap); - AddSkip(advancedWrap, st::settingsCheckboxesSkip); + SetupAdvancedNotifications(advancedWrap); } - const auto advanced = advancedWrap - ? AddButton( - advancedWrap, - lng_settings_advanced_notifications, - st::settingsButton).get() - : nullptr; if (!name->entity()->checked()) { preview->hide(anim::type::instant); @@ -206,11 +684,6 @@ void SetupNotificationsContent(not_null container) { } }, native->lifetime()); } - if (advanced) { - advanced->addClickHandler([=] { - Ui::show(Box()); - }); - } } void SetupNotifications(not_null container) { diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 208d5092e..f02296c8a 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -38,8 +38,6 @@ <(src_loc)/boxes/local_storage_box.h <(src_loc)/boxes/mute_settings_box.cpp <(src_loc)/boxes/mute_settings_box.h -<(src_loc)/boxes/notifications_box.cpp -<(src_loc)/boxes/notifications_box.h <(src_loc)/boxes/peer_list_box.cpp <(src_loc)/boxes/peer_list_box.h <(src_loc)/boxes/peer_list_controllers.cpp