diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 73dc78f23..538c247b9 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -489,6 +489,7 @@ hashtagClose: IconButton { stickerPanWidthMin: 64px; stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin); +stickerEffectWidthMin: 48px; stickerPanPadding: 11px; stickerPanDeleteIconBg: icon {{ "emoji/emoji_delete_bg", stickerPanDeleteBg }}; stickerPanDeleteIconFg: icon {{ "emoji/emoji_delete", stickerPanDeleteFg }}; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 1425f6dd8..cc718fae7 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -189,8 +189,10 @@ StickersListWidget::StickersListWidget( , _overBg(st::roundRadiusLarge, st().overBg) , _api(&session().mtp()) , _localSetsManager(std::make_unique(&session())) +, _customRecentIds(std::move(descriptor.customRecentList)) , _section(Section::Stickers) , _isMasks(_mode == Mode::Masks) +, _isEffects(_mode == Mode::MessageEffects) , _updateItemsTimer([=] { updateItems(); }) , _updateSetsTimer([=] { updateSets(); }) , _trendingAddBgOver( @@ -220,9 +222,11 @@ StickersListWidget::StickersListWidget( , _premiumMark(std::make_unique(&session())) , _searchRequestTimer([=] { sendSearchRequest(); }) { setMouseTracking(true); - setAttribute(Qt::WA_OpaquePaintEvent); + if (st().bg->c.alpha() > 0) { + setAttribute(Qt::WA_OpaquePaintEvent); + } - if (!_isMasks) { + if (!_isMasks && !_isEffects) { setupSearch(); } @@ -254,23 +258,30 @@ StickersListWidget::StickersListWidget( refreshStickers(); }, lifetime()); - session().data().stickers().recentUpdated( - _isMasks ? Data::StickersType::Masks : Data::StickersType::Stickers - ) | rpl::start_with_next([=] { - refreshRecent(); - }, lifetime()); + if (!_isEffects) { + session().data().stickers().recentUpdated(_isMasks + ? Data::StickersType::Masks + : Data::StickersType::Stickers + ) | rpl::start_with_next([=] { + refreshRecent(); + }, lifetime()); + } positionValue( ) | rpl::skip(1) | rpl::map_to( TabbedSelector::Action::Update ) | rpl::start_to_stream(_choosingUpdated, lifetime()); - rpl::merge( - Data::AmPremiumValue(&session()) | rpl::to_empty, - session().api().premium().cloudSetUpdated() - ) | rpl::start_with_next([=] { + if (_isEffects) { refreshStickers(); - }, lifetime()); + } else { + rpl::merge( + Data::AmPremiumValue(&session()) | rpl::to_empty, + session().api().premium().cloudSetUpdated() + ) | rpl::start_with_next([=] { + refreshStickers(); + }, lifetime()); + } } rpl::producer StickersListWidget::chosen() const { @@ -498,11 +509,14 @@ StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOff } int StickersListWidget::countDesiredHeight(int newWidth) { - if (newWidth <= st::stickerPanWidthMin) { + const auto minSize = _isEffects + ? st::stickerEffectWidthMin + : st::stickerPanWidthMin; + if (newWidth < 2 * minSize) { return 0; } auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left()); - auto columnCount = availableWidth / st::stickerPanWidthMin; + auto columnCount = availableWidth / minSize; auto singleWidth = availableWidth / columnCount; auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width); auto rowsRight = (fullWidth - columnCount * singleWidth) / 2; @@ -872,7 +886,9 @@ QRect StickersListWidget::stickerRect(int section, int sel) { void StickersListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); - p.fillRect(clip, st().bg); + if (st().bg->c.alpha() > 0) { + p.fillRect(clip, st().bg); + } paintStickers(p, clip); } @@ -1459,7 +1475,21 @@ void StickersListWidget::paintSticker( p.setOpacity(1.); } - if (premium) { + auto cornerPainted = false; + if (set.id == Data::Stickers::RecentSetId && !_cornerEmoji.empty()) { + Assert(index < _cornerEmoji.size()); + if (const auto emoji = _cornerEmoji[index]) { + const auto size = Ui::Emoji::GetSizeNormal(); + const auto ratio = style::DevicePixelRatio(); + const auto radius = st::roundRadiusSmall; + const auto position = pos + + QPoint(_singleSize.width(), _singleSize.height()) + - QPoint(size / ratio + radius, size / ratio + radius); + Ui::Emoji::Draw(p, emoji, size, position.x(), position.y()); + cornerPainted = true; + } + } + if (!cornerPainted && premium) { _premiumMark->paint( p, lottieFrame, @@ -1928,10 +1958,13 @@ void StickersListWidget::clearHeavyData() { void StickersListWidget::refreshStickers() { clearSelection(); - refreshMySets(); - refreshFeaturedSets(); - refreshSearchSets(); - + if (_isEffects) { + refreshEffects(); + } else { + refreshMySets(); + refreshFeaturedSets(); + refreshSearchSets(); + } resizeToWidth(width()); if (_footer) { @@ -1946,6 +1979,13 @@ void StickersListWidget::refreshStickers() { visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom()); } +void StickersListWidget::refreshEffects() { + auto wasSets = base::take(_mySets); + _mySets.reserve(1); + refreshRecentStickers(false); + takeHeavyData(_mySets, wasSets); +} + void StickersListWidget::refreshMySets() { auto wasSets = base::take(_mySets); _favedStickersMap.clear(); @@ -2107,7 +2147,27 @@ void StickersListWidget::refreshRecent() { } } +auto StickersListWidget::collectCustomRecents() -> std::vector { + _custom.clear(); + _cornerEmoji.clear(); + auto result = std::vector(); + + result.reserve(_customRecentIds.size()); + const auto owner = &session().data(); + for (const auto &descriptor : _customRecentIds) { + if (const auto document = descriptor.document; document->sticker()) { + result.push_back(Sticker{ document }); + _custom.push_back(false); + _cornerEmoji.push_back(Ui::Emoji::Find(descriptor.cornerEmoji)); + } + } + return result; +} + auto StickersListWidget::collectRecentStickers() -> std::vector { + if (_isEffects) { + return collectCustomRecents(); + } _custom.clear(); auto result = std::vector(); @@ -2435,7 +2495,9 @@ bool StickersListWidget::setHasTitle(const Set &set) const { return false; } else if (set.id == Data::Stickers::RecentSetId) { return !_mySets.empty() - && (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId)); + && (_isMasks + || _isEffects + || (_mySets[0].id == Data::Stickers::FavedSetId)); } return true; } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 34838b364..8dca5601e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -66,12 +66,19 @@ enum class StickersListMode { Masks, UserpicBuilder, ChatIntro, + MessageEffects, +}; + +struct StickerCustomRecentDescriptor { + not_null document; + QString cornerEmoji; }; struct StickersListDescriptor { std::shared_ptr show; StickersListMode mode = StickersListMode::Full; Fn paused; + std::vector customRecentList; const style::EmojiPan *st = nullptr; ComposeFeatures features; }; @@ -239,8 +246,10 @@ private: bool setHasTitle(const Set &set) const; bool stickerHasDeleteButton(const Set &set, int index) const; - std::vector collectRecentStickers(); + [[nodiscard]] std::vector collectRecentStickers(); + [[nodiscard]] std::vector collectCustomRecents(); void refreshRecentStickers(bool resize = true); + void refreshEffects(); void refreshFavedStickers(); enum class GroupStickersPlace { Visible, @@ -364,11 +373,13 @@ private: std::unique_ptr _localSetsManager; ChannelData *_megagroupSet = nullptr; uint64 _megagroupSetIdRequested = 0; + std::vector _customRecentIds; std::vector _mySets; std::vector _officialSets; std::vector _searchSets; int _featuredSetsCount = 0; std::vector _custom; + std::vector _cornerEmoji; base::flat_set> _favedStickersMap; std::weak_ptr _lottieRenderer; @@ -381,6 +392,7 @@ private: Section _section = Section::Stickers; const bool _isMasks; + const bool _isEffects; base::Timer _updateItemsTimer; base::Timer _updateSetsTimer; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index cdab73fb6..78baff83e 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -249,6 +249,9 @@ PossibleItemReactions::PossibleItemReactions( : recent(other.recent | ranges::views::transform([](const auto &value) { return *value; }) | ranges::to_vector) +, stickers(other.stickers | ranges::views::transform([](const auto &value) { + return *value; +}) | ranges::to_vector) , customAllowed(other.customAllowed) , tags(other.tags){ } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index a1254a6d7..98aab8d85 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -43,6 +43,7 @@ struct Reaction { struct PossibleItemReactionsRef { std::vector> recent; + std::vector> stickers; bool customAllowed = false; bool tags = false; }; @@ -52,6 +53,7 @@ struct PossibleItemReactions { explicit PossibleItemReactions(const PossibleItemReactionsRef &other); std::vector recent; + std::vector stickers; bool customAllowed = false; bool tags = false; }; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 9ab46ef14..506e05d13 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "ui/wrap/vertical_layout.h" #include "ui/text/text_custom_emoji.h" #include "ui/text/text_utilities.h" #include "ui/platform/ui_platform_utility.h" @@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_send.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_list_footer.h" +#include "chat_helpers/stickers_list_widget.h" #include "window/window_session_controller.h" #include "boxes/premium_preview_box.h" #include "mainwidget.h" @@ -217,6 +219,7 @@ Selector::Selector( child) { } +#if 0 // not ready Selector::Selector( not_null parent, const style::EmojiPan &st, @@ -237,6 +240,7 @@ Selector::Selector( close, child) { } +#endif Selector::Selector( not_null parent, @@ -931,8 +935,10 @@ void Selector::createList() { if (!_reactions.customAllowed) { st->bg = st::transparent; } - _list = _scroll->setOwnedWidget( - object_ptr(_scroll, EmojiListDescriptor{ + auto lists = _scroll->setOwnedWidget( + object_ptr(_scroll)); + _list = lists->add( + object_ptr(lists, EmojiListDescriptor{ .show = _show, .mode = _listMode, .paused = [] { return false; }, @@ -941,12 +947,35 @@ void Selector::createList() { : _recent), .customRecentFactory = _unifiedFactoryOwner->factory(), .st = st, - }) - ).data(); + })); + if (!_reactions.stickers.empty()) { + auto descriptors = ranges::views::all( + _reactions.stickers + ) | ranges::view::transform([](const Data::Reaction &reaction) { + return ChatHelpers::StickerCustomRecentDescriptor{ + reaction.selectAnimation, + reaction.title + }; + }) | ranges::to_vector; + _stickers = lists->add( + object_ptr( + lists, + StickersListDescriptor{ + .show = _show, + .mode = StickersListMode::MessageEffects, + .paused = [] { return false; }, + .customRecentList = std::move(descriptors), + .st = st, + })); + } _list->escapes() | rpl::start_to_stream(_escapes, _list->lifetime()); - _list->customChosen( + rpl::merge( + _list->customChosen(), + (_stickers + ? _stickers->chosen() + : rpl::never()) ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { _chosen.fire({ .id = _unifiedFactoryOwner->lookupReactionId(data.document->id), @@ -986,24 +1015,35 @@ void Selector::createList() { _shadow->show(); } const auto geometry = inner.marginsRemoved(_st.margin); - _list->move(0, 0); - _list->resizeToWidth(geometry.width()); + lists->move(0, 0); + lists->resizeToWidth(geometry.width()); _list->refreshEmoji(); - _list->show(); + lists->show(); const auto updateVisibleTopBottom = [=] { const auto scrollTop = _scroll->scrollTop(); const auto scrollBottom = scrollTop + _scroll->height(); - _list->setVisibleTopBottom(scrollTop, scrollBottom); + lists->setVisibleTopBottom(scrollTop, scrollBottom); }; _scroll->scrollTopChanges( - ) | rpl::start_with_next(updateVisibleTopBottom, _list->lifetime()); + ) | rpl::start_with_next(updateVisibleTopBottom, lists->lifetime()); _list->scrollToRequests( ) | rpl::start_with_next([=](int y) { _scroll->scrollToY(y); - _shadow->update(); + if (_shadow) { + _shadow->update(); + } }, _list->lifetime()); + if (_stickers) { + _stickers->scrollToRequests( + ) | rpl::start_with_next([=](int y) { + _scroll->scrollToY(_list->height() + y); + if (_shadow) { + _shadow->update(); + } + }, _stickers->lifetime()); + } _scroll->setGeometry(inner.marginsRemoved({ _st.margin.left(), diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 06de4e7cd..1dc49fb33 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -24,6 +24,7 @@ namespace ChatHelpers { class Show; class TabbedPanel; class EmojiListWidget; +class StickersListWidget; class StickersListFooter; enum class EmojiListMode; } // namespace ChatHelpers @@ -85,6 +86,7 @@ public: Fn close, IconFactory iconFactory = nullptr, bool child = false); +#if 0 // not ready Selector( not_null parent, const style::EmojiPan &st, @@ -93,6 +95,7 @@ public: std::vector recent, Fn close, bool child = false); +#endif ~Selector(); [[nodiscard]] bool useTransparency() const; @@ -193,6 +196,7 @@ private: Ui::ScrollArea *_scroll = nullptr; ChatHelpers::EmojiListWidget *_list = nullptr; + ChatHelpers::StickersListWidget *_stickers = nullptr; ChatHelpers::StickersListFooter *_footer = nullptr; std::unique_ptr _unifiedFactoryOwner; Ui::PlainShadow *_shadow = nullptr; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index f04662997..3cfc57d0b 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -169,10 +169,15 @@ void BottomRounded::paintEvent(QPaintEvent *e) { const auto premiumPossible = session->premiumPossible(); auto added = base::flat_set(); result.recent.reserve(effects.size()); + result.stickers.reserve(effects.size()); for (const auto &reaction : effects) { if (premiumPossible || !reaction.premium) { if (added.emplace(reaction.id).second) { - result.recent.push_back(&reaction); + if (reaction.aroundAnimation) { + result.recent.push_back(&reaction); + } else { + result.stickers.push_back(&reaction); + } } } }