diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4c31964b9..6bc6116e3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5346,6 +5346,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_edit" = "Edit Folder"; "lng_filters_setup_menu" = "Edit Folders"; "lng_filters_new_name" = "Folder name"; +"lng_filters_enable_animations" = "Enable animations"; +"lng_filters_disable_animations" = "Disable animations"; "lng_filters_add_chats" = "Add Chats"; "lng_filters_remove_chats" = "Add Chats to Exclude"; "lng_filters_include" = "Included chats"; diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 826052a82..d7a1636b3 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -52,7 +52,7 @@ public: ToggleChatsController( not_null window, ToggleAction action, - TextWithEntities title, + Data::ChatFilterTitle title, std::vector> chats, std::vector> additional); @@ -78,7 +78,6 @@ private: Ui::RpWidget *_addedBottomWidget = nullptr; ToggleAction _action = ToggleAction::Adding; - TextWithEntities _filterTitle; base::flat_set> _checkable; std::vector> _chats; std::vector> _additional; @@ -141,7 +140,7 @@ void InitFilterLinkHeader( not_null box, Fn adjust, Ui::FilterLinkHeaderType type, - TextWithEntities title, + Data::ChatFilterTitle title, QString iconEmoji, rpl::producer count, bool horizontalFilters) { @@ -149,18 +148,20 @@ void InitFilterLinkHeader( Ui::LookupFilterIconByEmoji( iconEmoji ).value_or(Ui::FilterIcon::Custom)).active; + const auto isStatic = title.isStatic; const auto makeContext = [=](Fn repaint) { return Core::MarkedTextContext{ .session = &box->peerListUiShow()->session(), .customEmojiRepaint = std::move(repaint), + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; auto header = Ui::MakeFilterLinkHeader(box, { .type = type, .title = TitleText(type)(tr::now), - .about = AboutText(type, title), + .about = AboutText(type, title.text), .makeAboutContext = makeContext, - .folderTitle = title, + .folderTitle = title.text, .folderIcon = icon, .badge = (type == Ui::FilterLinkHeaderType::AddingChats ? std::move(count) @@ -258,12 +259,11 @@ void ImportInvite( ToggleChatsController::ToggleChatsController( not_null window, ToggleAction action, - TextWithEntities title, + Data::ChatFilterTitle title, std::vector> chats, std::vector> additional) : _window(window) , _action(action) -, _filterTitle(title) , _chats(std::move(chats)) , _additional(std::move(additional)) { setStyleOverrides(&st::filterLinkChatsList); @@ -539,7 +539,7 @@ void ShowImportError( void ShowImportToast( base::weak_ptr weak, - TextWithEntities title, + Data::ChatFilterTitle title, Ui::FilterLinkHeaderType type, int added) { const auto strong = weak.get(); @@ -551,7 +551,7 @@ void ShowImportToast( ? tr::lng_filters_added_title : tr::lng_filters_updated_title; auto text = Ui::Text::Wrapped( - phrase(tr::now, lt_folder, title, Ui::Text::WithEntities), + phrase(tr::now, lt_folder, title.text, Ui::Text::WithEntities), EntityType::Bold); if (added > 0) { const auto phrase = created @@ -559,10 +559,12 @@ void ShowImportToast( : tr::lng_filters_updated_also; text.append('\n').append(phrase(tr::now, lt_count, added)); } + const auto isStatic = title.isStatic; const auto makeContext = [=](not_null widget) { return Core::MarkedTextContext{ .session = &strong->session(), .customEmojiRepaint = [=] { widget->update(); }, + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; strong->showToast({ @@ -595,7 +597,7 @@ void ProcessFilterInvite( base::weak_ptr weak, const QString &slug, FilterId filterId, - TextWithEntities title, + Data::ChatFilterTitle title, QString iconEmoji, std::vector> peers, std::vector> already) { @@ -637,16 +639,18 @@ void ProcessFilterInvite( raw->setRealContentHeight(box->heightValue()); + const auto isStatic = title.isStatic; const auto makeContext = [=](Fn update) { return Core::MarkedTextContext{ .session = &strong->session(), .customEmojiRepaint = update, + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; auto owned = Ui::FilterLinkProcessButton( box, type, - title, + title.text, makeContext, std::move(badge)); @@ -748,7 +752,7 @@ void CheckFilterInvite( if (!strong) { return; } - auto title = TextWithEntities(); + auto title = Data::ChatFilterTitle(); auto iconEmoji = QString(); auto filterId = FilterId(); auto peers = std::vector>(); @@ -767,7 +771,8 @@ void CheckFilterInvite( return result; }; result.match([&](const MTPDchatlists_chatlistInvite &data) { - title = ParseTextWithEntities(session, data.vtitle()); + title.text = ParseTextWithEntities(session, data.vtitle()); + title.isStatic = data.is_title_noanimate(); iconEmoji = data.vemoticon().value_or_empty(); peers = parseList(data.vpeers()); }, [&](const MTPDchatlists_chatlistInviteAlready &data) { @@ -832,7 +837,7 @@ void ProcessFilterUpdate( void ProcessFilterRemove( base::weak_ptr weak, - TextWithEntities title, + Data::ChatFilterTitle title, QString iconEmoji, std::vector> all, std::vector> suggest, @@ -867,16 +872,18 @@ void ProcessFilterRemove( raw->adjust(min, max, addedTop); }, type, title, iconEmoji, rpl::single(0), horizontalFilters); + const auto isStatic = title.isStatic; const auto makeContext = [=](Fn update) { return Core::MarkedTextContext{ .session = &strong->session(), .customEmojiRepaint = update, + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; auto owned = Ui::FilterLinkProcessButton( box, type, - title, + title.text, makeContext, std::move(badge)); diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h index 76c57d6d0..34933a7db 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.h +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -17,6 +17,7 @@ class SessionController; namespace Data { class ChatFilter; +struct ChatFilterTitle; } // namespace Data namespace Api { @@ -36,7 +37,7 @@ void ProcessFilterUpdate( void ProcessFilterRemove( base::weak_ptr weak, - TextWithEntities title, + Data::ChatFilterTitle title, QString iconEmoji, std::vector> all, std::vector> suggest, diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 75f056288..c2fdfa075 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.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" // primaryWindow +#include "core/ui_integration.h" #include "data/data_chat_filters.h" #include "data/data_premium_limits.h" #include "data/data_session.h" @@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" // Ui::Text::Bold +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/popup_menu.h" @@ -169,15 +171,26 @@ void ChangeFilterById( )).done([=, chat = history->peer->name(), name = filter.title()] { const auto account = not_null(&history->session().account()); if (const auto controller = Core::App().windowFor(account)) { - controller->showToast((add - ? tr::lng_filters_toast_add - : tr::lng_filters_toast_remove)( - tr::now, - lt_chat, - Ui::Text::Bold(chat), - lt_folder, - Ui::Text::Wrapped(name, EntityType::Bold), - Ui::Text::WithEntities)); + const auto isStatic = name.isStatic; + const auto textContext = [=](not_null widget) { + return Core::MarkedTextContext{ + .session = &history->session(), + .customEmojiRepaint = [=] { widget->update(); }, + .customEmojiLoopLimit = isStatic ? -1 : 0, + }; + }; + controller->showToast({ + .text = (add + ? tr::lng_filters_toast_add + : tr::lng_filters_toast_remove)( + tr::now, + lt_chat, + Ui::Text::Bold(chat), + lt_folder, + Ui::Text::Wrapped(name.text, EntityType::Bold), + Ui::Text::WithEntities), + .textContext = textContext, + }); } }).fail([=](const MTP::Error &error) { LOG(("API Error: failed to %1 a dialog to a folder. %2") @@ -279,7 +292,7 @@ void FillChooseFilterMenu( menu->st().menu, Ui::Menu::CreateAction( menu.get(), // todo filter emoji - Ui::Text::FixAmpersandInAction(filter.title().text), + Ui::Text::FixAmpersandInAction(filter.title().text.text), std::move(callback)), contains ? &st::mediaPlayerMenuCheck : nullptr, contains ? &st::mediaPlayerMenuCheck : nullptr); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index d202d6e6d..f9a05a557 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/filter_icons.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" +#include "ui/power_saving.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/fields/input_field.h" @@ -352,6 +353,8 @@ void EditFilterBox( rpl::variable hasLinks; rpl::variable chatlist; rpl::variable creating; + rpl::variable title; + rpl::variable staticTitle; rpl::variable colorIndex; }; const auto owner = &window->session().data(); @@ -359,6 +362,8 @@ void EditFilterBox( .rules = filter, .chatlist = filter.chatlist(), .creating = filter.title().empty(), + .title = filter.titleText(), + .staticTitle = filter.staticTitle(), }); state->colorIndex = filter.colorIndex().value_or(kNoTag); state->links = owner->chatsFilters().chatlistLinks(filter.id()), @@ -404,7 +409,7 @@ void EditFilterBox( }, box->lifetime()); const auto content = box->verticalLayout(); - const auto current = filter.title(); + const auto current = state->title.current(); const auto name = content->add( object_ptr( box, @@ -422,6 +427,44 @@ void EditFilterBox( const auto nameEditing = box->lifetime().make_state( NameEditing{ name }); + const auto staticTitle = Ui::CreateChild( + name, + QString()); + staticTitle->setClickedCallback([=] { + state->staticTitle = !state->staticTitle.current(); + }); + state->staticTitle.value() | rpl::start_with_next([=](bool value) { + staticTitle->setText(value + ? tr::lng_filters_enable_animations(tr::now) + : tr::lng_filters_disable_animations(tr::now)); + const auto paused = [=] { + using namespace Window; + return window->isGifPausedAtLeastFor(GifPauseReason::Layer); + }; + name->setCustomTextContext([=](Fn repaint) { + return std::any(Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = std::move(repaint), + .customEmojiLoopLimit = value ? -1 : 0, + }); + }, [paused] { + return On(PowerSaving::kEmojiChat) || paused(); + }, [paused] { + return On(PowerSaving::kChatSpoiler) || paused(); + }); + name->update(); + }, staticTitle->lifetime()); + + rpl::combine( + staticTitle->widthValue(), + name->widthValue() + ) | rpl::start_with_next([=](int inner, int outer) { + staticTitle->moveToRight( + st::windowFilterStaticTitlePosition.x(), + st::windowFilterStaticTitlePosition.y(), + outer); + }, staticTitle->lifetime()); + state->creating.value( ) | rpl::filter(!_1) | rpl::start_with_next([=] { nameEditing->custom = true; @@ -432,7 +475,13 @@ void EditFilterBox( if (!nameEditing->settingDefault) { nameEditing->custom = true; } + auto entered = name->getTextWithTags(); + state->title = TextWithEntities{ + std::move(entered.text), + TextUtilities::ConvertTextTagsToEntities(entered.tags), + }; }, name->lifetime()); + const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) { if (nameEditing->custom) { return; @@ -444,13 +493,11 @@ void EditFilterBox( nameEditing->settingDefault = false; } }; - const auto nameWithEntities = [=](bool upper = false) { - const auto entered = name->getTextWithTags(); - return TextWithEntities{ - (upper ? entered.text.toUpper() : entered.text), - TextUtilities::ConvertTextTagsToEntities(entered.tags), - }; - }; + + state->title.value( + ) | rpl::start_with_next([=](const TextWithEntities &value) { + staticTitle->setVisible(!value.entities.isEmpty()); + }, staticTitle->lifetime()); const auto outer = box->getDelegate()->outerContainer(); CreateIconSelector( @@ -595,10 +642,16 @@ void EditFilterBox( const auto palette = [](int i) { return Ui::EmptyUserpic::UserpicColor(i).color2; }; - name->changes() | rpl::start_with_next([=] { + const auto upperTitle = [=] { + auto value = state->title.current(); + value.text = value.text.toUpper(); + return value; + }; + state->title.changes( + ) | rpl::start_with_next([=] { tag->context.color = palette(state->colorIndex.current())->c; tag->frame = Ui::ChatsFilterTag( - nameWithEntities(true), + upperTitle(), tag->context); preview->update(); }, preview->lifetime()); @@ -615,7 +668,7 @@ void EditFilterBox( if (progress == 1) { tag->context.color = color->c; tag->frame = Ui::ChatsFilterTag( - nameWithEntities(true), + upperTitle(), tag->context); if (i == kNoTag) { tag->alpha = 0.; @@ -641,7 +694,7 @@ void EditFilterBox( buttons[now]->setSelectedProgress(progress); tag->context.color = anim::color(c1, c2, progress); tag->frame = Ui::ChatsFilterTag( - nameWithEntities(true), + upperTitle(), tag->context); tag->alpha = anim::interpolateF(a1, a2, progress); preview->update(); @@ -689,7 +742,9 @@ void EditFilterBox( } const auto collect = [=]() -> std::optional { - const auto title = nameWithEntities(); + auto title = state->title.current(); + const auto staticTitle = !title.entities.isEmpty() + && state->staticTitle.current(); const auto rules = data->current(); if (title.empty()) { name->showError(); @@ -708,7 +763,9 @@ void EditFilterBox( const auto colorIndex = (rawColorIndex >= kNoTag ? std::nullopt : std::make_optional(rawColorIndex)); - return rules.withTitle(title).withColorIndex(colorIndex); + return rules.withTitle( + { std::move(title), staticTitle } + ).withColorIndex(colorIndex); }; Ui::AddSubsectionTitle( diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index f69538c66..2ed72aa25 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -151,7 +151,10 @@ ExceptionRow::ExceptionRow( if (!filters.empty()) { filters.append(u", "_q); } - filters.append(filter.title()); + auto title = filter.title(); + filters.append(title.isStatic + ? Data::ForceCustomEmojiStatic(std::move(title.text)) + : std::move(title.text)); } } if (!filters.empty()) { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index 2ad88d1cc..61bbd85a0 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -483,7 +483,7 @@ private: const not_null _window; InviteLinkData _data; - TextWithEntities _filterTitle; + Data::ChatFilterTitle _filterTitle; base::flat_set> _filterChats; base::flat_map, QString> _denied; rpl::variable>> _selected; @@ -536,10 +536,12 @@ void LinkController::addHeader(not_null container) { }, verticalLayout->lifetime()); verticalLayout->add(std::move(icon.widget)); + const auto isStatic = _filterTitle.isStatic; const auto makeContext = [=](Fn update) { return Core::MarkedTextContext{ .session = &_window->session(), .customEmojiRepaint = update, + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; verticalLayout->add( @@ -552,7 +554,7 @@ void LinkController::addHeader(not_null container) { : tr::lng_filters_link_share_about( lt_folder, rpl::single(Ui::Text::Wrapped( - _filterTitle, + _filterTitle.text, EntityType::Bold)), Ui::Text::WithEntities)), st::settingsFilterDividerLabel, diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 2d15e4272..b7a88ed57 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -286,6 +286,9 @@ std::unique_ptr UiIntegration::createCustomEmoji( return std::make_unique( std::move(result), my->customEmojiLoopLimit); + } else if (my->customEmojiLoopLimit) { + return std::make_unique( + std::move(result)); } return result; } diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 8e54066c7..a53a8f5c6 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -40,9 +40,22 @@ constexpr auto kLoadExceptionsPerRequest = 100; } // namespace +TextWithEntities ForceCustomEmojiStatic(TextWithEntities text) { + for (auto &entity : text.entities) { + if (entity.type() == EntityType::CustomEmoji) { + entity = EntityInText( + EntityType::CustomEmoji, + entity.offset(), + entity.length(), + u"force-static:"_q + entity.data()); + } + } + return text; +} + ChatFilter::ChatFilter( FilterId id, - TextWithEntities title, + ChatFilterTitle title, QString iconEmoji, std::optional colorIndex, Flags flags, @@ -50,13 +63,15 @@ ChatFilter::ChatFilter( std::vector> pinned, base::flat_set> never) : _id(id) -, _title(std::move(title)) +, _title(std::move(title.text)) , _iconEmoji(std::move(iconEmoji)) , _colorIndex(colorIndex) , _always(std::move(always)) , _pinned(std::move(pinned)) , _never(std::move(never)) -, _flags(flags) { +, _flags(title.isStatic + ? (flags | Flag::StaticTitle) + : (flags & ~Flag::StaticTitle)) { } ChatFilter ChatFilter::FromTL( @@ -70,7 +85,8 @@ ChatFilter ChatFilter::FromTL( | (data.is_bots() ? Flag::Bots : Flag(0)) | (data.is_exclude_muted() ? Flag::NoMuted : Flag(0)) | (data.is_exclude_read() ? Flag::NoRead : Flag(0)) - | (data.is_exclude_archived() ? Flag::NoArchived : Flag(0)); + | (data.is_exclude_archived() ? Flag::NoArchived : Flag(0)) + | (data.is_title_noanimate() ? Flag::StaticTitle : Flag(0)); auto &&to_histories = ranges::views::transform([&]( const MTPInputPeer &input) { const auto peer = Data::PeerFromInputMTP(owner, input); @@ -96,7 +112,10 @@ ChatFilter ChatFilter::FromTL( }; return ChatFilter( data.vid().v, - Api::ParseTextWithEntities(&owner->session(), data.vtitle()), + { + Api::ParseTextWithEntities(&owner->session(), data.vtitle()), + data.is_title_noanimate(), + }, qs(data.vemoticon().value_or_empty()), data.vcolor() ? std::make_optional(data.vcolor()->v) @@ -105,7 +124,7 @@ ChatFilter ChatFilter::FromTL( std::move(list), std::move(pinned), { never.begin(), never.end() }); - }, [](const MTPDdialogFilterDefault &d) { + }, [](const MTPDdialogFilterDefault &) { return ChatFilter(); }, [&](const MTPDdialogFilterChatlist &data) { auto &&to_histories = ranges::views::transform([&]( @@ -144,13 +163,17 @@ ChatFilter ChatFilter::FromTL( }; return ChatFilter( data.vid().v, - Api::ParseTextWithEntities(&owner->session(), data.vtitle()), + { + Api::ParseTextWithEntities(&owner->session(), data.vtitle()), + data.is_title_noanimate(), + }, qs(data.vemoticon().value_or_empty()), data.vcolor() ? std::make_optional(data.vcolor()->v) : std::nullopt, (Flag::Chatlist - | (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())), + | (data.is_has_my_invites() ? Flag::HasMyLinks : Flag()) + | (data.is_title_noanimate() ? Flag::StaticTitle : Flag(0))), std::move(list), std::move(pinned), {}); @@ -163,9 +186,14 @@ ChatFilter ChatFilter::withId(FilterId id) const { return result; } -ChatFilter ChatFilter::withTitle(TextWithEntities title) const { +ChatFilter ChatFilter::withTitle(ChatFilterTitle title) const { auto result = *this; - result._title = std::move(title); + result._title = std::move(title.text); + if (title.isStatic) { + result._flags |= Flag::StaticTitle; + } else { + result._flags &= ~Flag::StaticTitle; + } return result; } @@ -219,7 +247,8 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { if (_flags & Flag::Chatlist) { using TLFlag = MTPDdialogFilterChatlist::Flag; const auto flags = TLFlag::f_emoticon - | (_colorIndex ? TLFlag::f_color : TLFlag(0)); + | (_colorIndex ? TLFlag::f_color : TLFlag(0)) + | (staticTitle() ? TLFlag::f_title_noanimate : TLFlag(0)); return MTP_dialogFilterChatlist( MTP_flags(flags), MTP_int(replaceId ? replaceId : _id), @@ -232,6 +261,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { using TLFlag = MTPDdialogFilter::Flag; const auto flags = TLFlag::f_emoticon | (_colorIndex ? TLFlag::f_color : TLFlag(0)) + | (staticTitle() ? TLFlag::f_title_noanimate : TLFlag(0)) | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) @@ -262,10 +292,14 @@ FilterId ChatFilter::id() const { return _id; } -TextWithEntities ChatFilter::title() const { +const TextWithEntities &ChatFilter::titleText() const { return _title; } +ChatFilterTitle ChatFilter::title() const { + return { _title, !!(_flags & Flag::StaticTitle) }; +} + QString ChatFilter::iconEmoji() const { return _iconEmoji; } @@ -278,6 +312,10 @@ ChatFilter::Flags ChatFilter::flags() const { return _flags; } +bool ChatFilter::staticTitle() const { + return _flags & Flag::StaticTitle; +} + bool ChatFilter::chatlist() const { return _flags & Flag::Chatlist; } @@ -669,7 +707,8 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { || (filter.hasMyLinks() != updated.hasMyLinks()); const auto listUpdated = rulesChanged || pinnedChanged - || (filter.title() != updated.title()) + || (filter.titleText() != updated.titleText()) + || (filter.staticTitle() != updated.staticTitle()) || (filter.iconEmoji() != updated.iconEmoji()); const auto colorChanged = filter.colorIndex() != updated.colorIndex(); const auto colorExistenceChanged = (!filter.colorIndex()) diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index ef9f5bc70..ef4c8deff 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -25,6 +25,17 @@ namespace Data { class Session; +struct ChatFilterTitle { + TextWithEntities text; + bool isStatic = false; + + [[nodiscard]] bool empty() const { + return text.empty(); + } +}; + +[[nodiscard]] TextWithEntities ForceCustomEmojiStatic(TextWithEntities text); + class ChatFilter final { public: enum class Flag : ushort { @@ -40,9 +51,10 @@ public: Chatlist = (1 << 8), HasMyLinks = (1 << 9), + StaticTitle = (1 << 10), - NewChats = (1 << 10), // Telegram Business exceptions. - ExistingChats = (1 << 11), + NewChats = (1 << 11), // Telegram Business exceptions. + ExistingChats = (1 << 12), }; friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; @@ -50,7 +62,7 @@ public: ChatFilter() = default; ChatFilter( FilterId id, - TextWithEntities title, + ChatFilterTitle title, QString iconEmoji, std::optional colorIndex, Flags flags, @@ -59,7 +71,7 @@ public: base::flat_set> never); [[nodiscard]] ChatFilter withId(FilterId id) const; - [[nodiscard]] ChatFilter withTitle(TextWithEntities title) const; + [[nodiscard]] ChatFilter withTitle(ChatFilterTitle title) const; [[nodiscard]] ChatFilter withColorIndex(std::optional) const; [[nodiscard]] ChatFilter withChatlist( bool chatlist, @@ -72,10 +84,12 @@ public: [[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const; [[nodiscard]] FilterId id() const; - [[nodiscard]] TextWithEntities title() const; + [[nodiscard]] ChatFilterTitle title() const; + [[nodiscard]] const TextWithEntities &titleText() const; [[nodiscard]] QString iconEmoji() const; [[nodiscard]] std::optional colorIndex() const; [[nodiscard]] Flags flags() const; + [[nodiscard]] bool staticTitle() const; [[nodiscard]] bool chatlist() const; [[nodiscard]] bool hasMyLinks() const; [[nodiscard]] const base::flat_set> &always() const; @@ -99,7 +113,7 @@ private: }; inline bool operator==(const ChatFilter &a, const ChatFilter &b) { - return (a.title() == b.title()) + return (a.titleText() == b.titleText()) && (a.iconEmoji() == b.iconEmoji()) && (a.colorIndex() == b.colorIndex()) && (a.flags() == b.flags()) diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index ed6f36515..d372674f5 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -113,6 +113,10 @@ private: return u"scaled-custom:"_q; } +[[nodiscard]] QString ForceStaticPrefix() { + return u"force-static:"_q; +} + [[nodiscard]] QString InternalPadding(QMargins value) { return value.isNull() ? QString() : QString(",%1,%2,%3,%4" ).arg(value.left() @@ -554,6 +558,10 @@ std::unique_ptr CustomEmojiManager::create( const auto original = data.mid(ScaledCustomPrefix().size()); return Ui::MakeScaledCustomEmoji( create(original, std::move(update), SizeTag::Large)); + } else if (data.startsWith(ForceStaticPrefix())) { + const auto original = data.mid(ForceStaticPrefix().size()); + return std::make_unique( + create(original, std::move(update), tag, sizeOverride)); } else if (data.startsWith(InternalPrefix())) { return internal(data); } else if (data.startsWith(UserpicEmojiPrefix())) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f581d421a..a77d0651f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -4181,7 +4181,7 @@ QImage *InnerWidget::cacheChatsFilterTag( auto roundedText = TextWithEntities(); auto colorIndex = -1; if (filter.id()) { - roundedText = filter.title(); + roundedText = filter.title().text; roundedText.text = roundedText.text.toUpper(); if (filter.colorIndex()) { colorIndex = *(filter.colorIndex()); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index d0e84da71..ddcc9071c 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -194,13 +194,15 @@ void FilterRowButton::updateData( bool ignoreCount) { Expects(_session != nullptr); + const auto title = filter.title(); _title.setMarkedText( st::contactsNameStyle, - filter.title(), + title.text, kMarkupTextOptions, Core::MarkedTextContext{ .session = _session, .customEmojiRepaint = [=] { update(); }, + .customEmojiLoopLimit = title.isStatic ? -1 : 0, }); _icon = Ui::ComputeFilterIcon(filter); _colorIndex = filter.colorIndex(); diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index b87131da0..713f06d8e 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -151,9 +151,10 @@ void ShowFiltersListMenu( for (auto i = 0; i < list.size(); ++i) { const auto &filter = list[i]; - auto text = filter.title().empty() + auto title = filter.title(); + auto text = title.text.empty() ? tr::lng_filters_all_short(tr::now) - : filter.title().text; // todo filter emoji + : title.text.text; // todo filter emoji const auto action = state->menu->addAction(std::move(text), [=] { if (i != active) { @@ -340,9 +341,12 @@ not_null AddChatFiltersTabsStrip( ranges::views::all( list ) | ranges::views::transform([](const Data::ChatFilter &filter) { - return filter.title().empty() + auto title = filter.title(); + return title.text.empty() ? TextWithEntities{ tr::lng_filters_all_short(tr::now) } - : filter.title(); + : title.isStatic + ? Data::ForceCustomEmojiStatic(title.text) + : title.text; }) | ranges::to_vector, context, paused); if (!sectionsChanged) { return; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 32e233a0d..30026da2c 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -285,8 +285,9 @@ windowFilterSmallRemoveRight: 10px; windowFilterNameInput: InputField(defaultInputField) { textMargins: margins(0px, 26px, 36px, 4px); } +windowFilterStaticTitlePosition: point(0px, 5px); windowFilterIconToggleSize: size(36px, 36px); -windowFilterIconTogglePosition: point(-4px, 12px); +windowFilterIconTogglePosition: point(-4px, 18px); windwoFilterIconPanelPosition: point(-2px, -1px); windowFilterIconSingle: size(44px, 42px); windowFilterIconPadding: margins(10px, 36px, 10px, 8px); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 9dfbd035d..d9ef9fbc2 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -218,7 +218,7 @@ void FiltersMenu::setupList() { _setup = prepareButton( _container, -1, - TextWithEntities{ tr::lng_filters_setup(tr::now) }, + { TextWithEntities{ tr::lng_filters_setup(tr::now) } }, Ui::FilterIcon::Edit); _reorder = std::make_unique(_list, &_scroll); @@ -249,13 +249,15 @@ base::unique_qptr FiltersMenu::prepareAll() { base::unique_qptr FiltersMenu::prepareButton( not_null container, FilterId id, - TextWithEntities title, + Data::ChatFilterTitle title, Ui::FilterIcon icon, bool toBeginning) { + const auto isStatic = title.isStatic; const auto makeContext = [=](Fn update) { return Core::MarkedTextContext{ .session = &_session->session(), .customEmojiRepaint = std::move(update), + .customEmojiLoopLimit = isStatic ? -1 : 0, }; }; const auto paused = [=] { @@ -264,7 +266,7 @@ base::unique_qptr FiltersMenu::prepareButton( }; auto prepared = object_ptr( container, - id ? title : TextWithEntities{ tr::lng_filters_all(tr::now) }, + id ? title.text : TextWithEntities{ tr::lng_filters_all(tr::now) }, st::windowFiltersButton, makeContext, paused); diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index c7eb26ef4..530cf0181 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/side_bar_button.h" #include "ui/widgets/scroll_area.h" +namespace Data { +struct ChatFilterTitle; +} // namespace Data + namespace Ui { class VerticalLayout; class VerticalLayoutReorder; @@ -44,7 +48,7 @@ private: [[nodiscard]] base::unique_qptr prepareButton( not_null container, FilterId id, - TextWithEntities title, + Data::ChatFilterTitle title, Ui::FilterIcon icon, bool toBeginning = false); void setupMainMenuIcon();