From 1aa8029a8a5ba51168f610084d3df5842d50d329 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Mar 2022 15:23:21 +0400 Subject: [PATCH] Improve notification preview settings design. --- Telegram/Resources/icons/settings/dino.svg | 23 ++ Telegram/Resources/langs/lang.strings | 6 +- Telegram/Resources/qrc/telegram/telegram.qrc | 1 + .../boxes/background_preview_box.cpp | 251 +--------------- Telegram/SourceFiles/boxes/boxes.style | 4 +- Telegram/SourceFiles/settings/settings.style | 8 + .../settings/settings_notifications.cpp | 265 ++++++++++++++--- .../ui/controls/chat_service_checkbox.cpp | 279 ++++++++++++++++++ .../ui/controls/chat_service_checkbox.h | 29 ++ .../window/notifications_manager.cpp | 2 +- Telegram/cmake/td_ui.cmake | 2 + 11 files changed, 582 insertions(+), 288 deletions(-) create mode 100644 Telegram/Resources/icons/settings/dino.svg create mode 100644 Telegram/SourceFiles/ui/controls/chat_service_checkbox.cpp create mode 100644 Telegram/SourceFiles/ui/controls/chat_service_checkbox.h diff --git a/Telegram/Resources/icons/settings/dino.svg b/Telegram/Resources/icons/settings/dino.svg new file mode 100644 index 000000000..24ebe83cf --- /dev/null +++ b/Telegram/Resources/icons/settings/dino.svg @@ -0,0 +1,23 @@ + + + Ava 162 Rexy + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0bbda64fe..177419c16 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -324,8 +324,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using."; "lng_settings_notify_title" = "Notifications for chats"; "lng_settings_desktop_notify" = "Desktop notifications"; -"lng_settings_show_name" = "Show sender's name"; -"lng_settings_show_preview" = "Show message preview"; "lng_settings_native_title" = "Native notifications"; "lng_settings_use_windows" = "Use Windows notifications"; "lng_settings_use_native_notifications" = "Use native notifications"; @@ -343,6 +341,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_events_pinned" = "Pinned messages"; "lng_settings_notifications_calls_title" = "Calls"; +"lng_notification_preview_title" = "Dino Rex"; +"lng_notification_preview_text" = "It's morning in Tokyo 😎"; +"lng_notification_show_name" = "Name"; +"lng_notification_show_text" = "Text"; "lng_notification_preview" = "You have a new message"; "lng_notification_reply" = "Reply"; "lng_notification_hide_all" = "Hide all"; diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index d8d40fbd3..9826d3107 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -67,6 +67,7 @@ ../../art/recording/recording_info_audio.svg ../../art/recording/recording_info_video_landscape.svg ../../art/recording/recording_info_video_portrait.svg + ../../icons/settings/dino.svg ../../icons/calls/hands.lottie diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 52bb145ac..1ca6faf39 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "window/themes/window_theme.h" +#include "ui/boxes/confirm_box.h" +#include "ui/controls/chat_service_checkbox.h" #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/toast/toast.h" @@ -28,9 +30,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_resolver.h" #include "data/data_file_origin.h" #include "base/unixtime.h" -#include "ui/boxes/confirm_box.h" #include "boxes/background_preview_box.h" #include "window/window_session_controller.h" +#include "settings/settings_common.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -42,231 +44,6 @@ namespace { constexpr auto kMaxWallPaperSlugLength = 255; -class ServiceCheck : public Ui::AbstractCheckView { -public: - ServiceCheck(const style::ServiceCheck &st, bool checked); - - QSize getSize() const override; - void paint( - Painter &p, - int left, - int top, - int outerWidth) override; - QImage prepareRippleMask() const override; - bool checkRippleStartPosition(QPoint position) const override; - -private: - class Generator { - public: - Generator(); - - void paintFrame( - Painter &p, - int left, - int top, - not_null st, - float64 toggled); - void invalidate(); - - private: - struct Frames { - QImage image; - std::vector ready; - }; - - not_null framesForStyle( - not_null st); - static void FillFrame( - QImage &image, - not_null st, - int index, - int count); - static void PaintFillingFrame( - Painter &p, - not_null st, - float64 progress); - static void PaintCheckingFrame( - Painter &p, - not_null st, - float64 progress); - - base::flat_map, Frames> _data; - rpl::lifetime _lifetime; - - }; - static Generator &Frames(); - - const style::ServiceCheck &_st; - -}; - -ServiceCheck::Generator::Generator() { - style::PaletteChanged( - ) | rpl::start_with_next([=] { - invalidate(); - }, _lifetime); -} - -auto ServiceCheck::Generator::framesForStyle( - not_null st) -> not_null { - if (const auto i = _data.find(st); i != _data.end()) { - return &i->second; - } - const auto result = &_data.emplace(st, Frames()).first->second; - const auto size = st->diameter; - const auto count = (st->duration / AnimationTimerDelta) + 2; - result->image = QImage( - QSize(count * size, size) * cIntRetinaFactor(), - QImage::Format_ARGB32_Premultiplied); - result->image.fill(Qt::transparent); - result->image.setDevicePixelRatio(cRetinaFactor()); - result->ready.resize(count); - return result; -} - -void ServiceCheck::Generator::FillFrame( - QImage &image, - not_null st, - int index, - int count) { - Expects(count > 1); - Expects(index >= 0 && index < count); - - Painter p(&image); - PainterHighQualityEnabler hq(p); - - p.translate(index * st->diameter, 0); - const auto progress = index / float64(count - 1); - if (progress > 0.5) { - PaintCheckingFrame(p, st, (progress - 0.5) * 2); - } else { - PaintFillingFrame(p, st, progress * 2); - } -} - -void ServiceCheck::Generator::PaintFillingFrame( - Painter &p, - not_null st, - float64 progress) { - const auto shift = progress * st->shift; - p.setBrush(st->color); - p.setPen(Qt::NoPen); - p.drawEllipse(QRectF( - shift, - shift, - st->diameter - 2 * shift, - st->diameter - 2 * shift)); - if (progress < 1.) { - const auto remove = progress * (st->diameter / 2. - st->thickness); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.setPen(Qt::NoPen); - p.setBrush(Qt::transparent); - p.drawEllipse(QRectF( - st->thickness + remove, - st->thickness + remove, - st->diameter - 2 * (st->thickness + remove), - st->diameter - 2 * (st->thickness + remove))); - } -} - -void ServiceCheck::Generator::PaintCheckingFrame( - Painter &p, - not_null st, - float64 progress) { - const auto shift = (1. - progress) * st->shift; - p.setBrush(st->color); - p.setPen(Qt::NoPen); - p.drawEllipse(QRectF( - shift, - shift, - st->diameter - 2 * shift, - st->diameter - 2 * shift)); - if (progress > 0.) { - const auto tip = QPointF(st->tip.x(), st->tip.y()); - const auto left = tip - QPointF(st->small, st->small) * progress; - const auto right = tip - QPointF(-st->large, st->large) * progress; - - p.setCompositionMode(QPainter::CompositionMode_Source); - p.setBrush(Qt::NoBrush); - auto pen = QPen(Qt::transparent); - pen.setWidth(st->stroke); - pen.setCapStyle(Qt::RoundCap); - pen.setJoinStyle(Qt::RoundJoin); - p.setPen(pen); - auto path = QPainterPath(); - path.moveTo(left); - path.lineTo(tip); - path.lineTo(right); - p.drawPath(path); - } -} - -void ServiceCheck::Generator::paintFrame( - Painter &p, - int left, - int top, - not_null st, - float64 toggled) { - const auto frames = framesForStyle(st); - auto &image = frames->image; - const auto count = int(frames->ready.size()); - const auto index = int(base::SafeRound(toggled * (count - 1))); - Assert(index >= 0 && index < count); - if (!frames->ready[index]) { - frames->ready[index] = true; - FillFrame(image, st, index, count); - } - const auto size = st->diameter; - const auto part = size * cIntRetinaFactor(); - p.drawImage( - QPoint(left, top), - image, - QRect(index * part, 0, part, part)); -} - -void ServiceCheck::Generator::invalidate() { - _data.clear(); -} - -ServiceCheck::Generator &ServiceCheck::Frames() { - static const auto Instance = Ui::CreateChild( - QCoreApplication::instance()); - return *Instance; -} - -ServiceCheck::ServiceCheck( - const style::ServiceCheck &st, - bool checked) -: AbstractCheckView(st.duration, checked, nullptr) -, _st(st) { -} - -QSize ServiceCheck::getSize() const { - const auto inner = QRect(0, 0, _st.diameter, _st.diameter); - return inner.marginsAdded(_st.margin).size(); -} - -void ServiceCheck::paint( - Painter &p, - int left, - int top, - int outerWidth) { - Frames().paintFrame( - p, - left + _st.margin.left(), - top + _st.margin.top(), - &_st, - currentAnimationValue()); -} - -QImage ServiceCheck::prepareRippleMask() const { - return QImage(); -} - -bool ServiceCheck::checkRippleStartPosition(QPoint position) const { - return false; -} - [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { return false; @@ -439,13 +216,13 @@ void BackgroundPreviewBox::prepare() { } void BackgroundPreviewBox::createBlurCheckbox() { - _blur.create( + _blur = Ui::MakeChatServiceCheckbox( this, tr::lng_background_blur(tr::now), st::backgroundCheckbox, - std::make_unique( - st::backgroundCheck, - _paper.isBlurred())); + st::backgroundCheck, + _paper.isBlurred(), + [=] { return _serviceBg.value_or(QColor(255, 255, 255, 0)); }); rpl::combine( sizeValue(), @@ -456,20 +233,6 @@ void BackgroundPreviewBox::createBlurCheckbox() { outer.height() - st::historyPaddingBottom - inner.height()); }, _blur->lifetime()); - _blur->paintRequest( - ) | rpl::filter([=] { - return _serviceBg.has_value(); - }) | rpl::start_with_next([=] { - Painter p(_blur.data()); - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - p.setBrush(*_serviceBg); - p.drawRoundedRect( - _blur->rect(), - st::historyMessageRadius, - st::historyMessageRadius); - }, _blur->lifetime()); - _blur->checkedChanges( ) | rpl::start_with_next([=](bool checked) { checkBlurAnimationStart(); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 43952c137..2525fc5be 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -855,14 +855,14 @@ backgroundCheckbox: Checkbox(defaultCheckbox) { width: -50px; margin: margins(0px, 0px, 0px, 0px); - textPosition: point(0px, 8px); + textPosition: point(0px, 6px); checkPosition: point(0px, 0px); style: semiboldTextStyle; } backgroundCheck: ServiceCheck { - margin: margins(12px, 8px, 8px, 8px); + margin: margins(10px, 6px, 8px, 6px); diameter: 18px; shift: 2px; thickness: 2px; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 58558bf48..0738868a4 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -369,3 +369,11 @@ settingsUsernameTop: 58px; settingsPeerToPeerSkip: 9px; settingsIconRadius: 6px; + +notifyPreviewMargins: margins(40px, 20px, 40px, 58px); +notifyPreviewUserpicSize: 36px; +notifyPreviewUserpicPosition: point(14px, 11px); +notifyPreviewTitlePosition: point(64px, 9px); +notifyPreviewTextPosition: point(64px, 30px); +notifyPreviewChecksSkip: 12px; +notifyPreviewBottomSkip: 9px; diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index e62f50008..70c46bf04 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_notifications.h" #include "settings/settings_common.h" +#include "ui/controls/chat_service_checkbox.h" #include "ui/effects/animations.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" +#include "ui/widgets/box_content_divider.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" @@ -18,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "window/notifications_manager.h" #include "window/window_session_controller.h" +#include "window/section_widget.h" #include "platform/platform_specific.h" #include "platform/platform_notifications_manager.h" #include "base/platform/base_platform_info.h" @@ -31,9 +34,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" +#include "styles/style_layers.h" +#include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" +#include #include namespace Settings { @@ -513,6 +519,199 @@ void NotificationsCount::SampleWidget::destroyDelayed() { #endif // Q_OS_UNIX && !Q_OS_MAC } +class NotifyPreview final { +public: + NotifyPreview(bool nameShown, bool previewShown); + + void setNameShown(bool shown); + void setPreviewShown(bool shown); + + int resizeGetHeight(int newWidth); + void paint(Painter &p, int x, int y); + +private: + int _width = 0; + int _height = 0; + bool _nameShown = false; + bool _previewShown = false; + Ui::RoundRect _roundRect; + Ui::Text::String _name, _title; + Ui::Text::String _text, _preview; + QSvgRenderer _userpic; + QImage _logo; + +}; + +NotifyPreview::NotifyPreview(bool nameShown, bool previewShown) +: _nameShown(nameShown) +, _previewShown(previewShown) +, _roundRect(st::boxRadius, st::msgInBg) +, _userpic(u":/gui/icons/settings/dino.svg"_q) +, _logo(Window::LogoNoMargin()) { + const auto ratio = style::DevicePixelRatio(); + _logo = _logo.scaledToWidth( + st::notifyPreviewUserpicSize * ratio, + Qt::SmoothTransformation); + _logo.setDevicePixelRatio(ratio); + + _name.setText( + st::settingsSubsectionTitle.style, + tr::lng_notification_preview_title(tr::now)); + _title.setText(st::settingsSubsectionTitle.style, AppName.utf16()); + + _text.setText( + st::boxTextStyle, + tr::lng_notification_preview_text(tr::now)); + _preview.setText( + st::boxTextStyle, + tr::lng_notification_preview(tr::now)); +} + +void NotifyPreview::setNameShown(bool shown) { + _nameShown = shown; +} + +void NotifyPreview::setPreviewShown(bool shown) { + _previewShown = shown; +} + +int NotifyPreview::resizeGetHeight(int newWidth) { + _width = newWidth; + _height = st::notifyPreviewUserpicPosition.y() + + st::notifyPreviewUserpicSize + + st::notifyPreviewUserpicPosition.y(); + const auto available = _width + - st::notifyPreviewTextPosition.x() + - st::notifyPreviewUserpicPosition.x(); + if (std::max(_text.maxWidth(), _preview.maxWidth()) >= available) { + _height += st::defaultTextStyle.font->height; + } + return _height; +} + +void NotifyPreview::paint(Painter &p, int x, int y) { + if (!_width || !_height) { + return; + } + p.translate(x, y); + const auto guard = gsl::finally([&] { p.translate(-x, -y); }); + + _roundRect.paint(p, { 0, 0, _width, _height }); + const auto userpic = QRect( + st::notifyPreviewUserpicPosition, + QSize{ st::notifyPreviewUserpicSize, st::notifyPreviewUserpicSize }); + + if (_nameShown) { + _userpic.render(&p, QRectF(userpic)); + } else { + p.drawImage(userpic.topLeft(), _logo); + } + + const auto &title = _nameShown ? _name : _title; + title.drawElided( + p, + st::notifyPreviewTitlePosition.x(), + st::notifyPreviewTitlePosition.y(), + _width - st::notifyPreviewTitlePosition.x() - userpic.x()); + + const auto &text = _previewShown ? _text : _preview; + text.drawElided( + p, + st::notifyPreviewTextPosition.x(), + st::notifyPreviewTextPosition.y(), + _width - st::notifyPreviewTextPosition.x() - userpic.x(), + 2); +} + +struct NotifyViewCheckboxes { + not_null*> wrap; + not_null name; + not_null preview; +}; + +NotifyViewCheckboxes SetupNotifyViewOptions( + not_null controller, + not_null container, + bool nameShown, + bool previewShown) { + using namespace rpl::mappers; + + auto wrap = container->add(object_ptr>( + container, + object_ptr(container))); + const auto widget = wrap->entity(); + + const auto makeCheckbox = [&](const QString &text, bool checked) { + return Ui::MakeChatServiceCheckbox( + widget, + text, + st::backgroundCheckbox, + st::backgroundCheck, + checked).release(); + }; + const auto name = makeCheckbox( + tr::lng_notification_show_name(tr::now), + nameShown); + const auto preview = makeCheckbox( + tr::lng_notification_show_text(tr::now), + previewShown); + + const auto view = widget->lifetime().make_state( + nameShown, + previewShown); + widget->widthValue( + ) | rpl::filter( + _1 >= (st::historyMinimalWidth / 2) + ) | rpl::start_with_next([=](int width) { + const auto margins = st::notifyPreviewMargins; + const auto bubblew = width - margins.left() - margins.right(); + const auto bubbleh = view->resizeGetHeight(bubblew); + const auto height = bubbleh + margins.top() + margins.bottom(); + widget->resize(width, height); + + const auto skip = st::notifyPreviewChecksSkip; + const auto checksWidth = name->width() + skip + preview->width(); + const auto checksLeft = (width - checksWidth) / 2; + const auto checksTop = height + - (margins.bottom() + name->height()) / 2; + name->move(checksLeft, checksTop); + preview->move(checksLeft + name->width() + skip, checksTop); + }, widget->lifetime()); + + widget->paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + Window::SectionWidget::PaintBackground( + controller, + controller->defaultChatTheme().get(), // #TODO themes + widget, + rect); + + Painter p(widget); + view->paint( + p, + st::notifyPreviewMargins.left(), + st::notifyPreviewMargins.top()); + }, widget->lifetime()); + + name->checkedChanges( + ) | rpl::start_with_next([=](bool checked) { + view->setNameShown(checked); + widget->update(); + }, name->lifetime()); + + preview->checkedChanges( + ) | rpl::start_with_next([=](bool checked) { + view->setPreviewShown(checked); + widget->update(); + }, preview->lifetime()); + + return { + .wrap = wrap, + .name = name, + .preview = preview, + }; +} + void SetupAdvancedNotifications( not_null controller, not_null container) { @@ -615,18 +814,6 @@ void SetupNotificationsContent( std::move(descriptor), std::move(checked))); }; - const auto addSlidingCheckbox = [&]( - rpl::producer label, - IconDescriptor &&descriptor, - rpl::producer checked) { - return container->add( - object_ptr>( - container, - checkbox( - std::move(label), - std::move(descriptor), - std::move(checked)))); - }; const auto &settings = Core::App().settings(); const auto desktopToggles = container->lifetime( ).make_state>(); @@ -635,15 +822,6 @@ void SetupNotificationsContent( { &st::settingsIconNotifications, kIconRed }, desktopToggles->events_starting_with(settings.desktopNotify())); - const auto name = addSlidingCheckbox( - tr::lng_settings_show_name(), - { &st::settingsIconUser, kIconLightOrange }, - rpl::single(settings.notifyView() <= NotifyView::ShowName)); - const auto preview = addSlidingCheckbox( - tr::lng_settings_show_preview(), - { &st::settingsIconAskQuestion, kIconGreen }, - rpl::single(settings.notifyView() <= NotifyView::ShowPreview)); - const auto soundToggles = container->lifetime( ).make_state>(); const auto sound = addCheckbox( @@ -663,8 +841,23 @@ void SetupNotificationsContent( settings.flashBounceNotify())); AddSkip(container); - AddDivider(container); - AddSkip(container); + + const auto checkboxes = SetupNotifyViewOptions( + controller, + container, + (settings.notifyView() <= NotifyView::ShowName), + (settings.notifyView() <= NotifyView::ShowPreview)); + const auto name = checkboxes.name; + const auto preview = checkboxes.preview; + const auto previewWrap = checkboxes.wrap; + const auto previewDivider = container->add( + object_ptr>( + container, + object_ptr(container))); + previewWrap->toggle(settings.desktopNotify(), anim::type::instant); + previewDivider->toggle(!settings.desktopNotify(), anim::type::instant); + + AddSkip(container, st::notifyPreviewBottomSkip); AddSubsectionTitle(container, tr::lng_settings_events_title()); auto joinSilent = rpl::single( @@ -768,13 +961,6 @@ void SetupNotificationsContent( SetupAdvancedNotifications(controller, advancedWrap); } - if (!name->entity()->toggled()) { - preview->hide(anim::type::instant); - } - if (!desktop->toggled()) { - name->hide(anim::type::instant); - preview->hide(anim::type::instant); - } if (native && advancedSlide && settings.nativeNotifications()) { advancedSlide->hide(anim::type::instant); } @@ -792,11 +978,12 @@ void SetupNotificationsContent( changed(Change::DesktopEnabled); }, desktop->lifetime()); - name->entity()->toggledChanges( + name->checkedChanges( ) | rpl::map([=](bool checked) { if (!checked) { + preview->setChecked(false); return NotifyView::ShowNothing; - } else if (!preview->entity()->toggled()) { + } else if (!preview->checked()) { return NotifyView::ShowName; } return NotifyView::ShowPreview; @@ -807,11 +994,12 @@ void SetupNotificationsContent( changed(Change::ViewParams); }, name->lifetime()); - preview->entity()->toggledChanges( + preview->checkedChanges( ) | rpl::map([=](bool checked) { if (checked) { + name->setChecked(true); return NotifyView::ShowPreview; - } else if (name->entity()->toggled()) { + } else if (name->checked()) { return NotifyView::ShowName; } return NotifyView::ShowNothing; @@ -858,15 +1046,14 @@ void SetupNotificationsContent( ) | rpl::start_with_next([=](Change change) { if (change == Change::DesktopEnabled) { desktopToggles->fire(Core::App().settings().desktopNotify()); - name->toggle( + previewWrap->toggle( Core::App().settings().desktopNotify(), anim::type::normal); - preview->toggle( - (Core::App().settings().desktopNotify() - && name->entity()->toggled()), + previewDivider->toggle( + !Core::App().settings().desktopNotify(), anim::type::normal); } else if (change == Change::ViewParams) { - preview->toggle(name->entity()->toggled(), anim::type::normal); + // } else if (change == Change::SoundEnabled) { soundToggles->fire(Core::App().settings().soundNotify()); } else if (change == Change::FlashBounceEnabled) { diff --git a/Telegram/SourceFiles/ui/controls/chat_service_checkbox.cpp b/Telegram/SourceFiles/ui/controls/chat_service_checkbox.cpp new file mode 100644 index 000000000..253b5177e --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/chat_service_checkbox.cpp @@ -0,0 +1,279 @@ +/* +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 "ui/controls/chat_service_checkbox.h" + +#include "ui/widgets/checkbox.h" +#include "styles/style_layers.h" + +#include + +namespace Ui { +namespace { + +constexpr auto kAnimationTimerDelta = crl::time(7); + +class ServiceCheck final : public AbstractCheckView { +public: + ServiceCheck(const style::ServiceCheck &st, bool checked); + + QSize getSize() const override; + void paint( + Painter &p, + int left, + int top, + int outerWidth) override; + QImage prepareRippleMask() const override; + bool checkRippleStartPosition(QPoint position) const override; + +private: + class Generator { + public: + Generator(); + + void paintFrame( + Painter &p, + int left, + int top, + not_null st, + float64 toggled); + void invalidate(); + + private: + struct Frames { + QImage image; + std::vector ready; + }; + + not_null framesForStyle( + not_null st); + static void FillFrame( + QImage &image, + not_null st, + int index, + int count); + static void PaintFillingFrame( + Painter &p, + not_null st, + float64 progress); + static void PaintCheckingFrame( + Painter &p, + not_null st, + float64 progress); + + base::flat_map, Frames> _data; + rpl::lifetime _lifetime; + + }; + static Generator &Frames(); + + const style::ServiceCheck &_st; + +}; + +ServiceCheck::Generator::Generator() { + style::PaletteChanged( + ) | rpl::start_with_next([=] { + invalidate(); + }, _lifetime); +} + +auto ServiceCheck::Generator::framesForStyle( + not_null st) -> not_null { + if (const auto i = _data.find(st); i != _data.end()) { + return &i->second; + } + const auto result = &_data.emplace(st, Frames()).first->second; + const auto size = st->diameter; + const auto count = (st->duration / kAnimationTimerDelta) + 2; + result->image = QImage( + QSize(count * size, size) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result->image.fill(Qt::transparent); + result->image.setDevicePixelRatio(style::DevicePixelRatio()); + result->ready.resize(count); + return result; +} + +void ServiceCheck::Generator::FillFrame( + QImage &image, + not_null st, + int index, + int count) { + Expects(count > 1); + Expects(index >= 0 && index < count); + + Painter p(&image); + PainterHighQualityEnabler hq(p); + + p.translate(index * st->diameter, 0); + const auto progress = index / float64(count - 1); + if (progress > 0.5) { + PaintCheckingFrame(p, st, (progress - 0.5) * 2); + } else { + PaintFillingFrame(p, st, progress * 2); + } +} + +void ServiceCheck::Generator::PaintFillingFrame( + Painter &p, + not_null st, + float64 progress) { + const auto shift = progress * st->shift; + p.setBrush(st->color); + p.setPen(Qt::NoPen); + p.drawEllipse(QRectF( + shift, + shift, + st->diameter - 2 * shift, + st->diameter - 2 * shift)); + if (progress < 1.) { + const auto remove = progress * (st->diameter / 2. - st->thickness); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setPen(Qt::NoPen); + p.setBrush(Qt::transparent); + p.drawEllipse(QRectF( + st->thickness + remove, + st->thickness + remove, + st->diameter - 2 * (st->thickness + remove), + st->diameter - 2 * (st->thickness + remove))); + } +} + +void ServiceCheck::Generator::PaintCheckingFrame( + Painter &p, + not_null st, + float64 progress) { + const auto shift = (1. - progress) * st->shift; + p.setBrush(st->color); + p.setPen(Qt::NoPen); + p.drawEllipse(QRectF( + shift, + shift, + st->diameter - 2 * shift, + st->diameter - 2 * shift)); + if (progress > 0.) { + const auto tip = QPointF(st->tip.x(), st->tip.y()); + const auto left = tip - QPointF(st->small, st->small) * progress; + const auto right = tip - QPointF(-st->large, st->large) * progress; + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setBrush(Qt::NoBrush); + auto pen = QPen(Qt::transparent); + pen.setWidth(st->stroke); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::RoundJoin); + p.setPen(pen); + auto path = QPainterPath(); + path.moveTo(left); + path.lineTo(tip); + path.lineTo(right); + p.drawPath(path); + } +} + +void ServiceCheck::Generator::paintFrame( + Painter &p, + int left, + int top, + not_null st, + float64 toggled) { + const auto frames = framesForStyle(st); + auto &image = frames->image; + const auto count = int(frames->ready.size()); + const auto index = int(base::SafeRound(toggled * (count - 1))); + Assert(index >= 0 && index < count); + if (!frames->ready[index]) { + frames->ready[index] = true; + FillFrame(image, st, index, count); + } + const auto size = st->diameter; + const auto part = size * style::DevicePixelRatio(); + p.drawImage( + QPoint(left, top), + image, + QRect(index * part, 0, part, part)); +} + +void ServiceCheck::Generator::invalidate() { + _data.clear(); +} + +ServiceCheck::Generator &ServiceCheck::Frames() { + static const auto Instance = Ui::CreateChild( + QCoreApplication::instance()); + return *Instance; +} + +ServiceCheck::ServiceCheck( + const style::ServiceCheck &st, + bool checked) +: AbstractCheckView(st.duration, checked, nullptr) +, _st(st) { +} + +QSize ServiceCheck::getSize() const { + const auto inner = QRect(0, 0, _st.diameter, _st.diameter); + return inner.marginsAdded(_st.margin).size(); +} + +void ServiceCheck::paint( + Painter &p, + int left, + int top, + int outerWidth) { + Frames().paintFrame( + p, + left + _st.margin.left(), + top + _st.margin.top(), + &_st, + currentAnimationValue()); +} + +QImage ServiceCheck::prepareRippleMask() const { + return QImage(); +} + +bool ServiceCheck::checkRippleStartPosition(QPoint position) const { + return false; +} + +void SetupBackground(not_null checkbox, Fn bg) { + checkbox->paintRequest( + ) | rpl::map( + bg ? bg : [] { return st::msgServiceBg->c; } + ) | rpl::filter([=](const QColor &color) { + return color.alpha() > 0; + }) | rpl::start_with_next([=](const QColor &color) { + Painter p(checkbox); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(color); + const auto radius = checkbox->height() / 2.; + p.drawRoundedRect(checkbox->rect(), radius, radius); + }, checkbox->lifetime()); +} + +} // namespace + +[[nodiscard]] object_ptr MakeChatServiceCheckbox( + QWidget *parent, + const QString &text, + const style::Checkbox &st, + const style::ServiceCheck &stCheck, + bool checked, + Fn bg) { + auto result = object_ptr( + parent, + text, + st, + std::make_unique(stCheck, checked)); + SetupBackground(result.data(), std::move(bg)); + return result; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/chat_service_checkbox.h b/Telegram/SourceFiles/ui/controls/chat_service_checkbox.h new file mode 100644 index 000000000..2c4533b03 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/chat_service_checkbox.h @@ -0,0 +1,29 @@ +/* +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 "base/object_ptr.h" + +namespace style { +struct Checkbox; +struct ServiceCheck; +} // namespace style + +namespace Ui { + +class Checkbox; + +[[nodiscard]] object_ptr MakeChatServiceCheckbox( + QWidget *parent, + const QString &text, + const style::Checkbox &st, + const style::ServiceCheck &stCheck, + bool checked, + Fn bg = nullptr); + +} // namespace Ui diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index c4945f70b..83ce22f13 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -954,7 +954,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) { && (item->out() || peer->isSelf()) && item->isFromScheduled(); const auto title = options.hideNameAndPhoto - ? qsl("Telegram Desktop") + ? AppName.utf16() : (scheduled && peer->isSelf()) ? tr::lng_notification_reminder(tr::now) : peer->name; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 3df88554b..5c558bb57 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -190,6 +190,8 @@ PRIVATE ui/chat/select_scroll_manager.h ui/controls/call_mute_button.cpp ui/controls/call_mute_button.h + ui/controls/chat_service_checkbox.cpp + ui/controls/chat_service_checkbox.h ui/controls/delete_message_context_action.cpp ui/controls/delete_message_context_action.h ui/controls/download_bar.cpp