diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 30c060f8b..826052a82 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/filters/edit_filter_links.h" // FilterChatStatusText #include "core/application.h" #include "core/core_settings.h" +#include "core/ui_integration.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_chat_filters.h" @@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/controls/filter_link_header.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" #include "ui/filter_icons.h" #include "ui/vertical_list.h" @@ -105,7 +107,7 @@ private: Unexpected("Ui::FilterLinkHeaderType in TitleText."); } -[[nodiscard]] TextWithEntities AboutText( // todo filter emoji +[[nodiscard]] TextWithEntities AboutText( Ui::FilterLinkHeaderType type, TextWithEntities title) { using Type = Ui::FilterLinkHeaderType; @@ -147,10 +149,17 @@ void InitFilterLinkHeader( Ui::LookupFilterIconByEmoji( iconEmoji ).value_or(Ui::FilterIcon::Custom)).active; + const auto makeContext = [=](Fn repaint) { + return Core::MarkedTextContext{ + .session = &box->peerListUiShow()->session(), + .customEmojiRepaint = std::move(repaint), + }; + }; auto header = Ui::MakeFilterLinkHeader(box, { .type = type, .title = TitleText(type)(tr::now), .about = AboutText(type, title), + .makeAboutContext = makeContext, .folderTitle = title, .folderIcon = icon, .badge = (type == Ui::FilterLinkHeaderType::AddingChats @@ -541,7 +550,7 @@ void ShowImportToast( const auto phrase = created ? tr::lng_filters_added_title : tr::lng_filters_updated_title; - auto text = Ui::Text::Wrapped( // todo filter emoji + auto text = Ui::Text::Wrapped( phrase(tr::now, lt_folder, title, Ui::Text::WithEntities), EntityType::Bold); if (added > 0) { @@ -550,7 +559,16 @@ void ShowImportToast( : tr::lng_filters_updated_also; text.append('\n').append(phrase(tr::now, lt_count, added)); } - strong->showToast(std::move(text)); + const auto makeContext = [=](not_null widget) { + return Core::MarkedTextContext{ + .session = &strong->session(), + .customEmojiRepaint = [=] { widget->update(); }, + }; + }; + strong->showToast({ + .text = std::move(text), + .textContext = makeContext, + }); } void HandleEnterInBox(not_null box) { @@ -619,10 +637,17 @@ void ProcessFilterInvite( raw->setRealContentHeight(box->heightValue()); + const auto makeContext = [=](Fn update) { + return Core::MarkedTextContext{ + .session = &strong->session(), + .customEmojiRepaint = update, + }; + }; auto owned = Ui::FilterLinkProcessButton( box, type, title, + makeContext, std::move(badge)); const auto button = owned.data(); @@ -842,10 +867,17 @@ void ProcessFilterRemove( raw->adjust(min, max, addedTop); }, type, title, iconEmoji, rpl::single(0), horizontalFilters); + const auto makeContext = [=](Fn update) { + return Core::MarkedTextContext{ + .session = &strong->session(), + .customEmojiRepaint = update, + }; + }; auto owned = Ui::FilterLinkProcessButton( box, type, title, + makeContext, std::move(badge)); const auto button = owned.data(); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 370a272ef..c6a483667 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_limits_box.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/message_field.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_channel.h" @@ -394,28 +395,28 @@ void EditFilterBox( tr::lng_filters_edit())); box->setCloseByOutsideClick(false); + const auto session = &window->session(); Data::AmPremiumValue( - &window->session() + session ) | rpl::start_with_next([=] { box->closeBox(); }, box->lifetime()); const auto content = box->verticalLayout(); + const auto current = filter.title(); const auto name = content->add( object_ptr( box, st::windowFilterNameInput, - tr::lng_filters_new_name(), - filter.title().text), // todo filter emoji + Ui::InputField::Mode::SingleLine, + tr::lng_filters_new_name()), st::markdownLinkFieldPadding); + InitMessageFieldHandlers(window, name, ChatHelpers::PauseReason::Layer); + name->setTextWithTags({ + current.text, + TextUtilities::ConvertEntitiesToTextTags(current.entities), + }, Ui::InputField::HistoryAction::Clear); name->setMaxLength(kMaxFilterTitleLength); - name->setInstantReplaces(Ui::InstantReplaces::Default()); - name->setInstantReplacesEnabled( - Core::App().settings().replaceEmojiValue()); - Ui::Emoji::SuggestionsController::Init( - box->getDelegate()->outerContainer(), - name, - &window->session()); const auto nameEditing = box->lifetime().make_state( NameEditing{ name }); @@ -672,8 +673,11 @@ void EditFilterBox( } const auto collect = [=]() -> std::optional { - // todo filter emoji - const auto title = TextWithEntities{ name->getLastText().trimmed() }; + const auto entered = name->getTextWithTags(); + const auto title = TextWithEntities{ + entered.text, + TextUtilities::ConvertTextTagsToEntities(entered.tags), + }; const auto rules = data->current(); if (title.empty()) { name->showError(); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index aeb7c3a24..f69538c66 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/filters/edit_filter_chats_list.h" +#include "core/ui_integration.h" #include "data/data_chat_filters.h" #include "data/data_premium_limits.h" #include "data/data_session.h" @@ -63,13 +64,27 @@ private: class ExceptionRow final : public ChatsListBoxController::Row { public: - explicit ExceptionRow(not_null history); + ExceptionRow( + not_null history, + not_null delegate); QString generateName() override; QString generateShortName() override; PaintRoundImageCallback generatePaintUserpicCallback( bool forceRound) override; + void paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected) override; + +private: + Ui::Text::String _filtersText; + }; class TypeController final : public PeerListController { @@ -126,15 +141,29 @@ Flag TypeRow::flag() const { return static_cast(id() & 0xFFFF); } -ExceptionRow::ExceptionRow(not_null history) : Row(history) { - auto filters = QStringList(); +ExceptionRow::ExceptionRow( + not_null history, + not_null delegate) +: Row(history) { + auto filters = TextWithEntities(); for (const auto &filter : history->owner().chatsFilters().list()) { if (filter.contains(history) && filter.id()) { - filters << filter.title().text; // todo filter emoji + if (!filters.empty()) { + filters.append(u", "_q); + } + filters.append(filter.title()); } } - if (!filters.isEmpty()) { - setCustomStatus(filters.join(", ")); + if (!filters.empty()) { + const auto repaint = [=] { delegate->peerListUpdateRow(this); }; + _filtersText.setMarkedText( + st::defaultTextStyle, + filters, + kMarkupTextOptions, + Core::MarkedTextContext{ + .session = &history->session(), + .customEmojiRepaint = repaint, + }); } else if (peer()->isSelf()) { setCustomStatus(tr::lng_saved_forward_here(tr::now)); } @@ -176,6 +205,37 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback( }; } +void ExceptionRow::paintStatusText( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int availableWidth, + int outerWidth, + bool selected) { + if (_filtersText.isEmpty()) { + Row::paintStatusText( + p, + st, + x, + y, + availableWidth, + outerWidth, + selected); + } else { + p.setPen(selected ? st.statusFgOver : st.statusFg); + _filtersText.draw(p, { + .position = { x, y }, + .outerWidth = outerWidth, + .availableWidth = availableWidth, + .palette = &st::defaultTextPalette, + .now = crl::now(), + .pausedEmoji = false, + .elisionLines = 1, + }); + } +} + TypeController::TypeController( not_null session, Flags options, @@ -418,7 +478,7 @@ void EditFilterChatsListController::prepareViewHook() { const auto rows = std::make_unique[]>(count); auto i = 0; for (const auto &history : _peers) { - rows[i++].emplace(history); + rows[i++].emplace(history, delegate()); } auto pointers = std::vector(); pointers.reserve(count); @@ -499,7 +559,7 @@ auto EditFilterChatsListController::createRow(not_null history) return nullptr; } return history->inChatList() - ? std::make_unique(history) + ? std::make_unique(history, delegate()) : nullptr; } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index 99f445c09..2ad88d1cc 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox. #include "boxes/peer_list_box.h" #include "boxes/premium_limits_box.h" +#include "core/ui_integration.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_chat_filters.h" @@ -535,6 +536,12 @@ void LinkController::addHeader(not_null container) { }, verticalLayout->lifetime()); verticalLayout->add(std::move(icon.widget)); + const auto makeContext = [=](Fn update) { + return Core::MarkedTextContext{ + .session = &_window->session(), + .customEmojiRepaint = update, + }; + }; verticalLayout->add( object_ptr>( verticalLayout, @@ -543,12 +550,14 @@ void LinkController::addHeader(not_null container) { (_data.url.isEmpty() ? tr::lng_filters_link_no_about(Ui::Text::WithEntities) : tr::lng_filters_link_share_about( - lt_folder, // todo filter emoji + lt_folder, rpl::single(Ui::Text::Wrapped( _filterTitle, EntityType::Bold)), Ui::Text::WithEntities)), - st::settingsFilterDividerLabel)), + st::settingsFilterDividerLabel, + st::defaultPopupMenu, + makeContext)), st::filterLinkDividerLabelPadding); verticalLayout->geometryValue( diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index eed3a2e04..eb07e2080 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -356,7 +356,8 @@ void ShareBox::prepare() { [this](FilterId id) { _inner->applyChatFilter(id); scrollToY(0); - }); + }, + Window::GifPauseReason::Layer); chatsFilters->lower(); chatsFilters->heightValue() | rpl::start_with_next([this](int h) { updateScrollSkips(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5329c0405..e71d0ad19 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1345,6 +1345,7 @@ void Widget::toggleFiltersMenu(bool enabled) { controller()->setActiveChatsFilter(id); } }, + Window::GifPauseReason::Any, controller(), true); raw->show(); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index bf11035e8..d0e84da71 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_limits_box.h" #include "boxes/premium_preview_box.h" #include "core/application.h" +#include "core/ui_integration.h" #include "data/data_chat_filters.h" #include "data/data_folder.h" #include "data/data_peer.h" @@ -57,14 +58,13 @@ public: FilterRowButton( not_null parent, not_null session, - const Data::ChatFilter &filter); - FilterRowButton( - not_null parent, const Data::ChatFilter &filter, - const QString &description); + const QString &description = {}); void setRemoved(bool removed); - void updateData(const Data::ChatFilter &filter); + void updateData( + const Data::ChatFilter &filter, + bool ignoreCount = false); void updateCount(const Data::ChatFilter &filter); [[nodiscard]] rpl::producer<> removeRequests() const; @@ -80,20 +80,13 @@ private: Normal, }; - FilterRowButton( - not_null parent, - Main::Session *session, - const Data::ChatFilter &filter, - const QString &description, - State state); - void paintEvent(QPaintEvent *e) override; void setup(const Data::ChatFilter &filter, const QString &status); void setState(State state, bool force = false); void updateButtonsVisibility(); - Main::Session *_session = nullptr; + const not_null _session; Ui::IconButton _remove; Ui::RoundButton _restore; @@ -177,50 +170,43 @@ struct FilterRow { FilterRowButton::FilterRowButton( not_null parent, not_null session, - const Data::ChatFilter &filter) -: FilterRowButton( - parent, - session, - filter, - ComputeCountString(session, filter), - State::Normal) { -} - -FilterRowButton::FilterRowButton( - not_null parent, const Data::ChatFilter &filter, const QString &description) -: FilterRowButton(parent, nullptr, filter, description, State::Suggested) { -} - -FilterRowButton::FilterRowButton( - not_null parent, - Main::Session *session, - const Data::ChatFilter &filter, - const QString &status, - State state) : RippleButton(parent, st::defaultRippleAnimation) , _session(session) , _remove(this, st::filtersRemove) , _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove) , _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd) -, _state(state) { +, _state(description.isEmpty() ? State::Normal : State::Suggested) { _restore.setTextTransform(Ui::RoundButton::TextTransform::NoTransform); _add.setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - setup(filter, status); + setup(filter, description.isEmpty() + ? ComputeCountString(session, filter) + : description); } void FilterRowButton::setRemoved(bool removed) { setState(removed ? State::Removed : State::Normal); } -void FilterRowButton::updateData(const Data::ChatFilter &filter) { +void FilterRowButton::updateData( + const Data::ChatFilter &filter, + bool ignoreCount) { Expects(_session != nullptr); - // todo filter emoji - _title.setText(st::contactsNameStyle, filter.title().text); + + _title.setMarkedText( + st::contactsNameStyle, + filter.title(), + kMarkupTextOptions, + Core::MarkedTextContext{ + .session = _session, + .customEmojiRepaint = [=] { update(); }, + }); _icon = Ui::ComputeFilterIcon(filter); _colorIndex = filter.colorIndex(); - updateCount(filter); + if (!ignoreCount) { + updateCount(filter); + } } void FilterRowButton::updateCount(const Data::ChatFilter &filter) { @@ -243,12 +229,9 @@ void FilterRowButton::setup( const Data::ChatFilter &filter, const QString &status) { resize(width(), st::defaultPeerListItem.height); - // todo filter emoji - _title.setText(st::contactsNameStyle, filter.title().text); - _status = status; - _icon = Ui::ComputeFilterIcon(filter); - _colorIndex = filter.colorIndex(); + _status = status; + updateData(filter, true); setState(_state, true); sizeValue() | rpl::start_with_next([=](QSize size) { @@ -648,6 +631,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { state->suggested = state->suggested.current() + 1; const auto button = aboutRows->add(object_ptr( aboutRows, + session, filter, suggestion.description)); button->addRequests( diff --git a/Telegram/SourceFiles/ui/controls/filter_link_header.cpp b/Telegram/SourceFiles/ui/controls/filter_link_header.cpp index 0e5a2a281..16c6cf398 100644 --- a/Telegram/SourceFiles/ui/controls/filter_link_header.cpp +++ b/Telegram/SourceFiles/ui/controls/filter_link_header.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/image/image_prepare.h" +#include "ui/text/text_utilities.h" #include "ui/painter.h" #include "ui/rp_widget.h" #include "ui/ui_utility.h" @@ -28,6 +29,11 @@ namespace { constexpr auto kBodyAnimationPart = 0.90; constexpr auto kTitleAdditionalScale = 0.05; +struct PreviewState { + Fn frame; + rpl::lifetime lifetime; +}; + class Widget final : public RpWidget { public: Widget( @@ -61,7 +67,7 @@ private: } _progress; rpl::variable _badge; - QImage _preview; + PreviewState _preview; QRectF _previewRect; QString _titleText; @@ -71,6 +77,7 @@ private: QPainterPath _titlePath; TextWithEntities _folderTitle; + Fn)> _makeContext; not_null _folderIcon; bool _horizontalFilters = false; @@ -80,55 +87,94 @@ private: }; -[[nodiscard]] QImage GeneratePreview( +[[nodiscard]] PreviewState GeneratePreview( not_null parent, const TextWithEntities &title, + Fn)> makeContext, int badge) { using Tabs = Ui::ChatsFiltersTabs; - auto owned = parent->lifetime().make_state>( - base::make_unique_q(parent, st::dialogsSearchTabs)); - const auto raw = owned->get(); + auto preview = PreviewState(); + + struct State { + State(not_null parent) + : tabs(parent, st::dialogsSearchTabs) { + } + + Tabs tabs; + QImage cache; + bool dirty = true; + }; + const auto state = preview.lifetime.make_state(parent); + preview.frame = [=] { + if (state->dirty) { + const auto raw = &state->tabs; + state->cache.fill(st::windowBg->c); + + auto p = QPainter(&state->cache); + Ui::RenderWidget(p, raw, QPoint(), raw->rect()); + + const auto &r = st::defaultEmojiSuggestions.fadeRight; + const auto &l = st::defaultEmojiSuggestions.fadeLeft; + const auto padding = st::filterLinkSubsectionTitlePadding.top(); + const auto w = raw->width(); + const auto h = raw->height(); + r.fill(p, QRect(w - r.width() - padding, 0, r.width(), h)); + l.fill(p, QRect(padding, 0, l.width(), h)); + p.fillRect(0, 0, padding, h, st::windowBg); + p.fillRect(w - padding, 0, padding, raw->height(), st::windowBg); + } + return state->cache; + }; + const auto raw = &state->tabs; + const auto repaint = [=] { + state->dirty = true; + }; raw->setSections({ - tr::lng_filters_name_people(tr::now), - title.text, // todo filter emoji - tr::lng_filters_name_unread(tr::now), - }); + TextWithEntities{ tr::lng_filters_name_people(tr::now) }, + title, + TextWithEntities{ tr::lng_filters_name_unread(tr::now) }, + }, makeContext(repaint)); raw->fitWidthToSections(); raw->setActiveSectionFast(1); raw->stopAnimation(); - auto result = QImage( + auto &result = state->cache; + result = QImage( raw->size() * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(style::DevicePixelRatio()); - result.fill(st::windowBg->c); - { - auto p = QPainter(&result); - Ui::RenderWidget(p, raw, QPoint(), raw->rect()); + raw->hide(); - const auto &r = st::defaultEmojiSuggestions.fadeRight; - const auto &l = st::defaultEmojiSuggestions.fadeLeft; - const auto padding = st::filterLinkSubsectionTitlePadding.top(); - const auto w = raw->width(); - const auto h = raw->height(); - r.fill(p, QRect(w - r.width() - padding, 0, r.width(), h)); - l.fill(p, QRect(padding, 0, l.width(), h)); - p.fillRect(0, 0, padding, h, st::windowBg); - p.fillRect(w - padding, 0, padding, raw->height(), st::windowBg); - } - owned->reset(); - return result; + style::PaletteChanged( + ) | rpl::start_with_next(repaint, preview.lifetime); + + return preview; } -[[nodiscard]] QImage GeneratePreview( +[[nodiscard]] PreviewState GeneratePreview( const TextWithEntities &title, + Fn)> makeContext, not_null icon, int badge) { + auto preview = PreviewState(); + + struct State { + QImage bg; + QImage composed; + Ui::Text::String string; + bool dirty = true; + }; + const auto state = preview.lifetime.make_state(); + const auto repaint = [=] { + state->dirty = true; + }; + const auto size = st::filterLinkPreview; const auto ratio = style::DevicePixelRatio(); const auto radius = st::filterLinkPreviewRadius; const auto full = QSize(size, size) * ratio; - auto result = QImage(full, QImage::Format_ARGB32_Premultiplied); + auto &result = state->bg; + result = QImage(full, QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(ratio); result.fill(st::windowBg->c); @@ -149,16 +195,19 @@ private: const auto myIconTop = st::filterLinkPreviewMyBottom - iconHeight; icon->paint(p, iconLeft, myIconTop, size); - const auto paintName = [&](const QString &text, int top) { - auto string = Ui::Text::String( - st.style, + const auto fillName = [=](const TextWithEntities &text) { + state->string = Ui::Text::String( + st::windowFiltersButton.style, text, - kDefaultTextOptions, - available); - string.draw(p, { + kMarkupTextOptions, + available, + makeContext(repaint)); + }; + const auto paintName = [=](QPainter &p, int top) { + state->string.draw(p, { .position = QPoint( std::max( - (column - string.maxWidth()) / 2, + (column - state->string.maxWidth()) / 2, skip), top), .outerWidth = available, @@ -169,9 +218,9 @@ private: }; p.setFont(st.style.font); p.setPen(st.textFg); - paintName(tr::lng_filters_all(tr::now), st::filterLinkPreviewAllTop); - p.setPen(st.textFgActive); - paintName(title.text, st::filterLinkPreviewMyTop); // todo filter emoji + fillName({ tr::lng_filters_all(tr::now) }); + paintName(p, st::filterLinkPreviewAllTop); + fillName(title); auto hq = PainterHighQualityEnabler(p); @@ -226,7 +275,19 @@ private: p.drawRoundedRect(0, 0, size, size, radius, radius); p.end(); - return Images::Round(std::move(result), Images::CornersMask(radius)); + result = Images::Round(std::move(result), Images::CornersMask(radius)); + + preview.frame = [=] { + if (state->dirty) { + state->composed = state->bg; + auto p = QPainter(&state->composed); + p.setPen(st.textFgActive); + paintName(p, st::filterLinkPreviewMyTop); + } + return state->composed; + }; + + return preview; } Widget::Widget( @@ -236,7 +297,9 @@ Widget::Widget( , _about(CreateChild( this, rpl::single(descriptor.about.value()), - st::filterLinkAbout)) + st::filterLinkAbout, + st::defaultPopupMenu, + descriptor.makeAboutContext)) , _close(CreateChild(this, st::boxTitleClose)) , _aboutPadding(st::boxRowPadding) , _badge(std::move(descriptor.badge)) @@ -244,19 +307,15 @@ Widget::Widget( , _titleFont(st::boxTitle.style.font) , _titlePadding(st::filterLinkTitlePadding) , _folderTitle(descriptor.folderTitle) +, _makeContext(descriptor.makeAboutContext) , _folderIcon(descriptor.folderIcon) , _horizontalFilters(descriptor.horizontalFilters) { setMinimumHeight(st::boxTitleHeight); refreshTitleText(); setTitlePosition(st::boxTitlePosition.x(), st::boxTitlePosition.y()); - style::PaletteChanged( - ) | rpl::start_with_next([this] { - _preview = QImage(); - }, lifetime()); - _badge.changes() | rpl::start_with_next([this] { - _preview = QImage(); + _preview = PreviewState(); update(); }, lifetime()); } @@ -332,8 +391,9 @@ QRectF Widget::previewRect( float64 topProgress, float64 sizeProgress) const { if (_horizontalFilters) { - const auto size = (_preview.size() / style::DevicePixelRatio()) - * sizeProgress; + const auto size = (_preview.frame + ? (_preview.frame().size() / style::DevicePixelRatio()) + : QSize()) * sizeProgress; return QRectF( (width() - size.width()) / 2., st::filterLinkPreviewTop * 1.5 * topProgress, @@ -355,16 +415,27 @@ void Widget::paintEvent(QPaintEvent *e) { p.setOpacity(_progress.body); if (_progress.top) { auto hq = PainterHighQualityEnabler(p); - if (_preview.isNull()) { + if (!_preview.frame) { const auto badge = _badge.current(); + const auto makeContext = [=](Fn repaint) { + return _makeContext([=] { repaint(); update(); }); + }; if (_horizontalFilters) { - _preview = GeneratePreview(this, _folderTitle, badge); + _preview = GeneratePreview( + this, + _folderTitle, + makeContext, + badge); Widget::resizeEvent(nullptr); } else { - _preview = GeneratePreview(_folderTitle, _folderIcon, badge); + _preview = GeneratePreview( + _folderTitle, + makeContext, + _folderIcon, + badge); } } - p.drawImage(_previewRect, _preview); + p.drawImage(_previewRect, _preview.frame()); } p.resetTransform(); @@ -415,13 +486,14 @@ object_ptr FilterLinkProcessButton( not_null parent, FilterLinkHeaderType type, TextWithEntities title, + Fn)> makeContext, rpl::producer badge) { const auto st = &st::filterInviteBox.button; const auto badgeSt = &st::filterInviteButtonBadgeStyle; auto result = object_ptr(parent, rpl::single(u""_q), *st); struct Data { - QString text; + TextWithEntities text; QString badge; }; auto data = std::move( @@ -429,32 +501,41 @@ object_ptr FilterLinkProcessButton( ) | rpl::map([=](int count) { const auto badge = count ? QString::number(count) : QString(); const auto with = [&](QString badge) { - return rpl::map([=](QString text) { + return rpl::map([=](TextWithEntities text) { return Data{ text, badge }; }); }; switch (type) { case FilterLinkHeaderType::AddingFilter: return badge.isEmpty() - ? tr::lng_filters_by_link_add_no() | with(QString()) + ? tr::lng_filters_by_link_add_no( + Ui::Text::WithEntities + ) | with(QString()) : tr::lng_filters_by_link_add_button( lt_folder, - rpl::single(title.text) // todo filter emoji + rpl::single(title), + Ui::Text::WithEntities ) | with(badge); case FilterLinkHeaderType::AddingChats: return badge.isEmpty() - ? tr::lng_filters_by_link_join_no() | with(QString()) + ? tr::lng_filters_by_link_join_no( + Ui::Text::WithEntities + ) | with(QString()) : tr::lng_filters_by_link_and_join_button( lt_count, - rpl::single(float64(count))) | with(badge); + rpl::single(float64(count)), + Ui::Text::WithEntities) | with(badge); case FilterLinkHeaderType::AllAdded: - return tr::lng_box_ok() | with(QString()); + return tr::lng_box_ok(Ui::Text::WithEntities) | with(QString()); case FilterLinkHeaderType::Removing: return badge.isEmpty() - ? tr::lng_filters_by_link_remove_button() | with(QString()) + ? tr::lng_filters_by_link_remove_button( + Ui::Text::WithEntities + ) | with(QString()) : tr::lng_filters_by_link_and_quit_button( lt_count, - rpl::single(float64(count))) | with(badge); + rpl::single(float64(count)), + Ui::Text::WithEntities) | with(badge); } Unexpected("Type in FilterLinkProcessButton."); }) | rpl::flatten_latest(); @@ -520,7 +601,11 @@ object_ptr FilterLinkProcessButton( }, label->lifetime()); std::move(data) | rpl::start_with_next([=](Data data) { - label->text.setText(st::filterInviteButtonStyle, data.text); + label->text.setMarkedText( + st::filterInviteButtonStyle, + data.text, + kMarkupTextOptions, + makeContext([=] { label->update(); })); label->badge.setText(st::filterInviteButtonBadgeStyle, data.badge); label->update(); }, label->lifetime()); diff --git a/Telegram/SourceFiles/ui/controls/filter_link_header.h b/Telegram/SourceFiles/ui/controls/filter_link_header.h index 2687ec3f2..55d092387 100644 --- a/Telegram/SourceFiles/ui/controls/filter_link_header.h +++ b/Telegram/SourceFiles/ui/controls/filter_link_header.h @@ -26,6 +26,7 @@ struct FilterLinkHeaderDescriptor { base::required type; base::required title; base::required about; + Fn)> makeAboutContext; base::required folderTitle; not_null folderIcon; rpl::producer badge; @@ -46,6 +47,7 @@ struct FilterLinkHeader { not_null parent, FilterLinkHeaderType type, TextWithEntities title, + Fn)> makeContext, rpl::producer badge); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp index 8a35fa520..8c5e01f93 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp @@ -46,22 +46,25 @@ ChatsFiltersTabs::ChatsFiltersTabs( } bool ChatsFiltersTabs::setSectionsAndCheckChanged( - std::vector &§ions) { + std::vector &§ions, + const std::any &context, + Fn paused) { const auto &was = sectionsRef(); const auto changed = [&] { if (was.size() != sections.size()) { return true; } for (auto i = 0; i < sections.size(); i++) { - if (was[i].label.toString() != sections[i]) { + if (was[i].label.toTextWithEntities() != sections[i]) { return true; } } return false; }(); if (changed) { - Ui::DiscreteSlider::setSections(std::move(sections)); + Ui::DiscreteSlider::setSections(std::move(sections), context); } + _emojiPaused = std::move(paused); return changed; } @@ -171,6 +174,7 @@ void ChatsFiltersTabs::paintEvent(QPaintEvent *e) { const auto clip = e->rect(); const auto range = getCurrentActiveRange(); const auto activeIndex = activeSection(); + const auto now = crl::now(); auto index = 0; auto raisedIndex = -1; @@ -225,6 +229,8 @@ void ChatsFiltersTabs::paintEvent(QPaintEvent *e) { .position = QPoint(labelLeft, _st.labelTop), .outerWidth = width(), .availableWidth = section.label.maxWidth(), + .now = now, + .pausedEmoji = _emojiPaused && _emojiPaused(), }); { const auto it = _unreadCounts.find(index); diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h index 0f6df2331..f6080c62f 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h @@ -27,7 +27,10 @@ public: not_null parent, const style::SettingsSlider &st); - bool setSectionsAndCheckChanged(std::vector &§ions); + bool setSectionsAndCheckChanged( + std::vector &§ions, + const std::any &context, + Fn paused); void fitWidthToSections() override; void setUnreadCount(int index, int unreadCount, bool muted); @@ -84,6 +87,7 @@ private: std::optional _bar; std::optional _barActive; std::optional _lockCache; + Fn _emojiPaused; int _reordering = 0; diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index b3f58194b..b87131da0 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/filters/edit_filter_box.h" #include "boxes/premium_limits_box.h" #include "core/application.h" +#include "core/ui_integration.h" #include "data/data_chat_filters.h" #include "data/data_peer_values.h" // Data::AmPremiumValue. #include "data/data_premium_limits.h" @@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_folders.h" +#include "ui/power_saving.h" #include "ui/ui_utility.h" #include "ui/widgets/chat_filters_tabs_slider_reorder.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" @@ -177,6 +179,7 @@ not_null AddChatFiltersTabsStrip( not_null parent, not_null session, Fn choose, + ChatHelpers::PauseReason pauseLevel, Window::SessionController *controller, bool trackActiveFilterAndUnreadAndReorder) { @@ -325,14 +328,22 @@ not_null AddChatFiltersTabsStrip( if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) { return; } + const auto context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { slider->update(); }, + }; + const auto paused = [=] { + return On(PowerSaving::kEmojiChat) + || controller->isGifPausedAtLeastFor(pauseLevel); + }; const auto sectionsChanged = slider->setSectionsAndCheckChanged( ranges::views::all( list ) | ranges::views::transform([](const Data::ChatFilter &filter) { return filter.title().empty() - ? tr::lng_filters_all_short(tr::now) - : filter.title().text; // todo filter emoji - }) | ranges::to_vector); + ? TextWithEntities{ tr::lng_filters_all_short(tr::now) } + : filter.title(); + }) | ranges::to_vector, context, paused); if (!sectionsChanged) { return; } diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h index 7b42651ed..9cbc79f22 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace ChatHelpers { +enum class PauseReason; +} // namespace ChatHelpers + namespace Main { class Session; } // namespace Main @@ -25,6 +29,7 @@ not_null AddChatFiltersTabsStrip( not_null parent, not_null session, Fn choose, + ChatHelpers::PauseReason pauseLevel, Window::SessionController *controller = nullptr, bool trackActiveFilterAndUnreadAndReorder = false); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index da9202fb0..9dfbd035d 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_main_menu.h" #include "window/window_peer_menu.h" #include "main/main_session.h" +#include "core/ui_integration.h" #include "data/data_session.h" #include "data/data_chat_filters.h" #include "data/data_user.h" @@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/popup_menu.h" #include "ui/boxes/confirm_box.h" +#include "ui/power_saving.h" #include "ui/ui_utility.h" #include "boxes/filters/edit_filter_box.h" #include "boxes/premium_limits_box.h" @@ -46,7 +48,7 @@ FiltersMenu::FiltersMenu( : _session(session) , _parent(parent) , _outer(_parent) -, _menu(&_outer, QString(), st::windowFiltersMainMenu) +, _menu(&_outer, TextWithEntities(), st::windowFiltersMainMenu) , _scroll(&_outer) , _container( _scroll.setOwnedWidget( @@ -250,10 +252,22 @@ base::unique_qptr FiltersMenu::prepareButton( TextWithEntities title, Ui::FilterIcon icon, bool toBeginning) { + const auto makeContext = [=](Fn update) { + return Core::MarkedTextContext{ + .session = &_session->session(), + .customEmojiRepaint = std::move(update), + }; + }; + const auto paused = [=] { + return On(PowerSaving::kEmojiChat) + || _session->isGifPausedAtLeastFor(Window::GifPauseReason::Any); + }; auto prepared = object_ptr( container, - id ? title.text : tr::lng_filters_all(tr::now), // todo filter emoji - st::windowFiltersButton); + id ? title : TextWithEntities{ tr::lng_filters_all(tr::now) }, + st::windowFiltersButton, + makeContext, + paused); auto added = toBeginning ? container->insert(0, std::move(prepared)) : container->add(std::move(prepared)); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 24c1007f0..0efcec3ca 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2241,7 +2241,8 @@ QPointer ShowForwardMessagesBox( [=](FilterId id) { *lastFilterId = id; applyFilter(box, id); - }); + }, + Window::GifPauseReason::Layer); chatsFilters->lower(); rpl::combine( chatsFilters->heightValue(), diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ab8d8fcfd..b063197b5 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ab8d8fcfd265f94b2a63f94a0f1796b907818b74 +Subproject commit b063197b5d05de96754894630e45c8b3f95ade62