Show sticker effects in a StickerListWidget.

This commit is contained in:
John Preston 2024-05-12 12:49:28 +04:00
parent 5fb7992b04
commit b92a05011f
8 changed files with 163 additions and 34 deletions

View file

@ -489,6 +489,7 @@ hashtagClose: IconButton {
stickerPanWidthMin: 64px; stickerPanWidthMin: 64px;
stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin); stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin);
stickerEffectWidthMin: 48px;
stickerPanPadding: 11px; stickerPanPadding: 11px;
stickerPanDeleteIconBg: icon {{ "emoji/emoji_delete_bg", stickerPanDeleteBg }}; stickerPanDeleteIconBg: icon {{ "emoji/emoji_delete_bg", stickerPanDeleteBg }};
stickerPanDeleteIconFg: icon {{ "emoji/emoji_delete", stickerPanDeleteFg }}; stickerPanDeleteIconFg: icon {{ "emoji/emoji_delete", stickerPanDeleteFg }};

View file

@ -189,8 +189,10 @@ StickersListWidget::StickersListWidget(
, _overBg(st::roundRadiusLarge, st().overBg) , _overBg(st::roundRadiusLarge, st().overBg)
, _api(&session().mtp()) , _api(&session().mtp())
, _localSetsManager(std::make_unique<LocalStickersManager>(&session())) , _localSetsManager(std::make_unique<LocalStickersManager>(&session()))
, _customRecentIds(std::move(descriptor.customRecentList))
, _section(Section::Stickers) , _section(Section::Stickers)
, _isMasks(_mode == Mode::Masks) , _isMasks(_mode == Mode::Masks)
, _isEffects(_mode == Mode::MessageEffects)
, _updateItemsTimer([=] { updateItems(); }) , _updateItemsTimer([=] { updateItems(); })
, _updateSetsTimer([=] { updateSets(); }) , _updateSetsTimer([=] { updateSets(); })
, _trendingAddBgOver( , _trendingAddBgOver(
@ -220,9 +222,11 @@ StickersListWidget::StickersListWidget(
, _premiumMark(std::make_unique<StickerPremiumMark>(&session())) , _premiumMark(std::make_unique<StickerPremiumMark>(&session()))
, _searchRequestTimer([=] { sendSearchRequest(); }) { , _searchRequestTimer([=] { sendSearchRequest(); }) {
setMouseTracking(true); setMouseTracking(true);
setAttribute(Qt::WA_OpaquePaintEvent); if (st().bg->c.alpha() > 0) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
if (!_isMasks) { if (!_isMasks && !_isEffects) {
setupSearch(); setupSearch();
} }
@ -254,23 +258,30 @@ StickersListWidget::StickersListWidget(
refreshStickers(); refreshStickers();
}, lifetime()); }, lifetime());
session().data().stickers().recentUpdated( if (!_isEffects) {
_isMasks ? Data::StickersType::Masks : Data::StickersType::Stickers session().data().stickers().recentUpdated(_isMasks
) | rpl::start_with_next([=] { ? Data::StickersType::Masks
refreshRecent(); : Data::StickersType::Stickers
}, lifetime()); ) | rpl::start_with_next([=] {
refreshRecent();
}, lifetime());
}
positionValue( positionValue(
) | rpl::skip(1) | rpl::map_to( ) | rpl::skip(1) | rpl::map_to(
TabbedSelector::Action::Update TabbedSelector::Action::Update
) | rpl::start_to_stream(_choosingUpdated, lifetime()); ) | rpl::start_to_stream(_choosingUpdated, lifetime());
rpl::merge( if (_isEffects) {
Data::AmPremiumValue(&session()) | rpl::to_empty,
session().api().premium().cloudSetUpdated()
) | rpl::start_with_next([=] {
refreshStickers(); refreshStickers();
}, lifetime()); } else {
rpl::merge(
Data::AmPremiumValue(&session()) | rpl::to_empty,
session().api().premium().cloudSetUpdated()
) | rpl::start_with_next([=] {
refreshStickers();
}, lifetime());
}
} }
rpl::producer<FileChosen> StickersListWidget::chosen() const { rpl::producer<FileChosen> StickersListWidget::chosen() const {
@ -498,11 +509,14 @@ StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOff
} }
int StickersListWidget::countDesiredHeight(int newWidth) { int StickersListWidget::countDesiredHeight(int newWidth) {
if (newWidth <= st::stickerPanWidthMin) { const auto minSize = _isEffects
? st::stickerEffectWidthMin
: st::stickerPanWidthMin;
if (newWidth < 2 * minSize) {
return 0; return 0;
} }
auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left()); auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left());
auto columnCount = availableWidth / st::stickerPanWidthMin; auto columnCount = availableWidth / minSize;
auto singleWidth = availableWidth / columnCount; auto singleWidth = availableWidth / columnCount;
auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width); auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width);
auto rowsRight = (fullWidth - columnCount * singleWidth) / 2; auto rowsRight = (fullWidth - columnCount * singleWidth) / 2;
@ -872,7 +886,9 @@ QRect StickersListWidget::stickerRect(int section, int sel) {
void StickersListWidget::paintEvent(QPaintEvent *e) { void StickersListWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
auto clip = e->rect(); auto clip = e->rect();
p.fillRect(clip, st().bg); if (st().bg->c.alpha() > 0) {
p.fillRect(clip, st().bg);
}
paintStickers(p, clip); paintStickers(p, clip);
} }
@ -1459,7 +1475,21 @@ void StickersListWidget::paintSticker(
p.setOpacity(1.); 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( _premiumMark->paint(
p, p,
lottieFrame, lottieFrame,
@ -1928,10 +1958,13 @@ void StickersListWidget::clearHeavyData() {
void StickersListWidget::refreshStickers() { void StickersListWidget::refreshStickers() {
clearSelection(); clearSelection();
refreshMySets(); if (_isEffects) {
refreshFeaturedSets(); refreshEffects();
refreshSearchSets(); } else {
refreshMySets();
refreshFeaturedSets();
refreshSearchSets();
}
resizeToWidth(width()); resizeToWidth(width());
if (_footer) { if (_footer) {
@ -1946,6 +1979,13 @@ void StickersListWidget::refreshStickers() {
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom()); visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
} }
void StickersListWidget::refreshEffects() {
auto wasSets = base::take(_mySets);
_mySets.reserve(1);
refreshRecentStickers(false);
takeHeavyData(_mySets, wasSets);
}
void StickersListWidget::refreshMySets() { void StickersListWidget::refreshMySets() {
auto wasSets = base::take(_mySets); auto wasSets = base::take(_mySets);
_favedStickersMap.clear(); _favedStickersMap.clear();
@ -2107,7 +2147,27 @@ void StickersListWidget::refreshRecent() {
} }
} }
auto StickersListWidget::collectCustomRecents() -> std::vector<Sticker> {
_custom.clear();
_cornerEmoji.clear();
auto result = std::vector<Sticker>();
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<Sticker> { auto StickersListWidget::collectRecentStickers() -> std::vector<Sticker> {
if (_isEffects) {
return collectCustomRecents();
}
_custom.clear(); _custom.clear();
auto result = std::vector<Sticker>(); auto result = std::vector<Sticker>();
@ -2435,7 +2495,9 @@ bool StickersListWidget::setHasTitle(const Set &set) const {
return false; return false;
} else if (set.id == Data::Stickers::RecentSetId) { } else if (set.id == Data::Stickers::RecentSetId) {
return !_mySets.empty() return !_mySets.empty()
&& (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId)); && (_isMasks
|| _isEffects
|| (_mySets[0].id == Data::Stickers::FavedSetId));
} }
return true; return true;
} }

View file

@ -66,12 +66,19 @@ enum class StickersListMode {
Masks, Masks,
UserpicBuilder, UserpicBuilder,
ChatIntro, ChatIntro,
MessageEffects,
};
struct StickerCustomRecentDescriptor {
not_null<DocumentData*> document;
QString cornerEmoji;
}; };
struct StickersListDescriptor { struct StickersListDescriptor {
std::shared_ptr<Show> show; std::shared_ptr<Show> show;
StickersListMode mode = StickersListMode::Full; StickersListMode mode = StickersListMode::Full;
Fn<bool()> paused; Fn<bool()> paused;
std::vector<StickerCustomRecentDescriptor> customRecentList;
const style::EmojiPan *st = nullptr; const style::EmojiPan *st = nullptr;
ComposeFeatures features; ComposeFeatures features;
}; };
@ -239,8 +246,10 @@ private:
bool setHasTitle(const Set &set) const; bool setHasTitle(const Set &set) const;
bool stickerHasDeleteButton(const Set &set, int index) const; bool stickerHasDeleteButton(const Set &set, int index) const;
std::vector<Sticker> collectRecentStickers(); [[nodiscard]] std::vector<Sticker> collectRecentStickers();
[[nodiscard]] std::vector<Sticker> collectCustomRecents();
void refreshRecentStickers(bool resize = true); void refreshRecentStickers(bool resize = true);
void refreshEffects();
void refreshFavedStickers(); void refreshFavedStickers();
enum class GroupStickersPlace { enum class GroupStickersPlace {
Visible, Visible,
@ -364,11 +373,13 @@ private:
std::unique_ptr<LocalStickersManager> _localSetsManager; std::unique_ptr<LocalStickersManager> _localSetsManager;
ChannelData *_megagroupSet = nullptr; ChannelData *_megagroupSet = nullptr;
uint64 _megagroupSetIdRequested = 0; uint64 _megagroupSetIdRequested = 0;
std::vector<StickerCustomRecentDescriptor> _customRecentIds;
std::vector<Set> _mySets; std::vector<Set> _mySets;
std::vector<Set> _officialSets; std::vector<Set> _officialSets;
std::vector<Set> _searchSets; std::vector<Set> _searchSets;
int _featuredSetsCount = 0; int _featuredSetsCount = 0;
std::vector<bool> _custom; std::vector<bool> _custom;
std::vector<EmojiPtr> _cornerEmoji;
base::flat_set<not_null<DocumentData*>> _favedStickersMap; base::flat_set<not_null<DocumentData*>> _favedStickersMap;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer; std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
@ -381,6 +392,7 @@ private:
Section _section = Section::Stickers; Section _section = Section::Stickers;
const bool _isMasks; const bool _isMasks;
const bool _isEffects;
base::Timer _updateItemsTimer; base::Timer _updateItemsTimer;
base::Timer _updateSetsTimer; base::Timer _updateSetsTimer;

View file

@ -249,6 +249,9 @@ PossibleItemReactions::PossibleItemReactions(
: recent(other.recent | ranges::views::transform([](const auto &value) { : recent(other.recent | ranges::views::transform([](const auto &value) {
return *value; return *value;
}) | ranges::to_vector) }) | ranges::to_vector)
, stickers(other.stickers | ranges::views::transform([](const auto &value) {
return *value;
}) | ranges::to_vector)
, customAllowed(other.customAllowed) , customAllowed(other.customAllowed)
, tags(other.tags){ , tags(other.tags){
} }

View file

@ -43,6 +43,7 @@ struct Reaction {
struct PossibleItemReactionsRef { struct PossibleItemReactionsRef {
std::vector<not_null<const Reaction*>> recent; std::vector<not_null<const Reaction*>> recent;
std::vector<not_null<const Reaction*>> stickers;
bool customAllowed = false; bool customAllowed = false;
bool tags = false; bool tags = false;
}; };
@ -52,6 +53,7 @@ struct PossibleItemReactions {
explicit PossibleItemReactions(const PossibleItemReactionsRef &other); explicit PossibleItemReactions(const PossibleItemReactionsRef &other);
std::vector<Reaction> recent; std::vector<Reaction> recent;
std::vector<Reaction> stickers;
bool customAllowed = false; bool customAllowed = false;
bool tags = false; bool tags = false;
}; };

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/platform/ui_platform_utility.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 "menu/menu_send.h"
#include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/emoji_list_widget.h"
#include "chat_helpers/stickers_list_footer.h" #include "chat_helpers/stickers_list_footer.h"
#include "chat_helpers/stickers_list_widget.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "mainwidget.h" #include "mainwidget.h"
@ -217,6 +219,7 @@ Selector::Selector(
child) { child) {
} }
#if 0 // not ready
Selector::Selector( Selector::Selector(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const style::EmojiPan &st, const style::EmojiPan &st,
@ -237,6 +240,7 @@ Selector::Selector(
close, close,
child) { child) {
} }
#endif
Selector::Selector( Selector::Selector(
not_null<QWidget*> parent, not_null<QWidget*> parent,
@ -931,8 +935,10 @@ void Selector::createList() {
if (!_reactions.customAllowed) { if (!_reactions.customAllowed) {
st->bg = st::transparent; st->bg = st::transparent;
} }
_list = _scroll->setOwnedWidget( auto lists = _scroll->setOwnedWidget(
object_ptr<EmojiListWidget>(_scroll, EmojiListDescriptor{ object_ptr<Ui::VerticalLayout>(_scroll));
_list = lists->add(
object_ptr<EmojiListWidget>(lists, EmojiListDescriptor{
.show = _show, .show = _show,
.mode = _listMode, .mode = _listMode,
.paused = [] { return false; }, .paused = [] { return false; },
@ -941,12 +947,35 @@ void Selector::createList() {
: _recent), : _recent),
.customRecentFactory = _unifiedFactoryOwner->factory(), .customRecentFactory = _unifiedFactoryOwner->factory(),
.st = st, .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<StickersListWidget>(
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->escapes() | rpl::start_to_stream(_escapes, _list->lifetime());
_list->customChosen( rpl::merge(
_list->customChosen(),
(_stickers
? _stickers->chosen()
: rpl::never<ChatHelpers::FileChosen>())
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
_chosen.fire({ _chosen.fire({
.id = _unifiedFactoryOwner->lookupReactionId(data.document->id), .id = _unifiedFactoryOwner->lookupReactionId(data.document->id),
@ -986,24 +1015,35 @@ void Selector::createList() {
_shadow->show(); _shadow->show();
} }
const auto geometry = inner.marginsRemoved(_st.margin); const auto geometry = inner.marginsRemoved(_st.margin);
_list->move(0, 0); lists->move(0, 0);
_list->resizeToWidth(geometry.width()); lists->resizeToWidth(geometry.width());
_list->refreshEmoji(); _list->refreshEmoji();
_list->show(); lists->show();
const auto updateVisibleTopBottom = [=] { const auto updateVisibleTopBottom = [=] {
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
const auto scrollBottom = scrollTop + _scroll->height(); const auto scrollBottom = scrollTop + _scroll->height();
_list->setVisibleTopBottom(scrollTop, scrollBottom); lists->setVisibleTopBottom(scrollTop, scrollBottom);
}; };
_scroll->scrollTopChanges( _scroll->scrollTopChanges(
) | rpl::start_with_next(updateVisibleTopBottom, _list->lifetime()); ) | rpl::start_with_next(updateVisibleTopBottom, lists->lifetime());
_list->scrollToRequests( _list->scrollToRequests(
) | rpl::start_with_next([=](int y) { ) | rpl::start_with_next([=](int y) {
_scroll->scrollToY(y); _scroll->scrollToY(y);
_shadow->update(); if (_shadow) {
_shadow->update();
}
}, _list->lifetime()); }, _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({ _scroll->setGeometry(inner.marginsRemoved({
_st.margin.left(), _st.margin.left(),

View file

@ -24,6 +24,7 @@ namespace ChatHelpers {
class Show; class Show;
class TabbedPanel; class TabbedPanel;
class EmojiListWidget; class EmojiListWidget;
class StickersListWidget;
class StickersListFooter; class StickersListFooter;
enum class EmojiListMode; enum class EmojiListMode;
} // namespace ChatHelpers } // namespace ChatHelpers
@ -85,6 +86,7 @@ public:
Fn<void(bool fast)> close, Fn<void(bool fast)> close,
IconFactory iconFactory = nullptr, IconFactory iconFactory = nullptr,
bool child = false); bool child = false);
#if 0 // not ready
Selector( Selector(
not_null<QWidget*> parent, not_null<QWidget*> parent,
const style::EmojiPan &st, const style::EmojiPan &st,
@ -93,6 +95,7 @@ public:
std::vector<DocumentId> recent, std::vector<DocumentId> recent,
Fn<void(bool fast)> close, Fn<void(bool fast)> close,
bool child = false); bool child = false);
#endif
~Selector(); ~Selector();
[[nodiscard]] bool useTransparency() const; [[nodiscard]] bool useTransparency() const;
@ -193,6 +196,7 @@ private:
Ui::ScrollArea *_scroll = nullptr; Ui::ScrollArea *_scroll = nullptr;
ChatHelpers::EmojiListWidget *_list = nullptr; ChatHelpers::EmojiListWidget *_list = nullptr;
ChatHelpers::StickersListWidget *_stickers = nullptr;
ChatHelpers::StickersListFooter *_footer = nullptr; ChatHelpers::StickersListFooter *_footer = nullptr;
std::unique_ptr<UnifiedFactoryOwner> _unifiedFactoryOwner; std::unique_ptr<UnifiedFactoryOwner> _unifiedFactoryOwner;
Ui::PlainShadow *_shadow = nullptr; Ui::PlainShadow *_shadow = nullptr;

View file

@ -169,10 +169,15 @@ void BottomRounded::paintEvent(QPaintEvent *e) {
const auto premiumPossible = session->premiumPossible(); const auto premiumPossible = session->premiumPossible();
auto added = base::flat_set<Data::ReactionId>(); auto added = base::flat_set<Data::ReactionId>();
result.recent.reserve(effects.size()); result.recent.reserve(effects.size());
result.stickers.reserve(effects.size());
for (const auto &reaction : effects) { for (const auto &reaction : effects) {
if (premiumPossible || !reaction.premium) { if (premiumPossible || !reaction.premium) {
if (added.emplace(reaction.id).second) { 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);
}
} }
} }
} }