Implement background emoji selector.
This commit is contained in:
parent
bcdb1bdfd2
commit
cb6698cf4a
17 changed files with 445 additions and 118 deletions
|
@ -790,6 +790,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages.";
|
||||
"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages.";
|
||||
"lng_settings_color_emoji" = "Add icons to replies";
|
||||
"lng_settings_color_emoji_remove" = "Remove icon";
|
||||
"lng_settings_color_emoji_off" = "Off";
|
||||
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
|
||||
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
|
||||
|
|
|
@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos(
|
|||
_userPhotosRequests.emplace(user, requestId);
|
||||
}
|
||||
|
||||
auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
|
||||
switch (type) {
|
||||
case EmojiListType::Profile: return _profileEmojiList;
|
||||
case EmojiListType::Group: return _groupEmojiList;
|
||||
case EmojiListType::Background: return _backgroundEmojiList;
|
||||
}
|
||||
Unexpected("Type in PeerPhoto::emojiList.");
|
||||
}
|
||||
|
||||
auto PeerPhoto::emojiList(EmojiListType type) const
|
||||
-> const EmojiListData & {
|
||||
return const_cast<PeerPhoto*>(this)->emojiList(type);
|
||||
}
|
||||
|
||||
void PeerPhoto::requestEmojiList(EmojiListType type) {
|
||||
if (_requestIdEmojiList) {
|
||||
auto &list = emojiList(type);
|
||||
if (list.requestId) {
|
||||
return;
|
||||
}
|
||||
const auto isGroup = (type == EmojiListType::Group);
|
||||
const auto d = [=](const MTPEmojiList &result) {
|
||||
_requestIdEmojiList = 0;
|
||||
result.match([](const MTPDemojiListNotModified &data) {
|
||||
}, [&](const MTPDemojiList &data) {
|
||||
auto &list = isGroup ? _profileEmojiList : _groupEmojiList;
|
||||
list = ranges::views::all(
|
||||
data.vdocument_id().v
|
||||
) | ranges::views::transform(&MTPlong::v) | ranges::to_vector;
|
||||
});
|
||||
const auto send = [&](auto &&request) {
|
||||
return _api.request(
|
||||
std::move(request)
|
||||
).done([=](const MTPEmojiList &result) {
|
||||
auto &list = emojiList(type);
|
||||
list.requestId = 0;
|
||||
result.match([](const MTPDemojiListNotModified &data) {
|
||||
}, [&](const MTPDemojiList &data) {
|
||||
list.list = ranges::views::all(
|
||||
data.vdocument_id().v
|
||||
) | ranges::views::transform(
|
||||
&MTPlong::v
|
||||
) | ranges::to_vector;
|
||||
});
|
||||
}).fail([=] {
|
||||
emojiList(type).requestId = 0;
|
||||
}).send();
|
||||
};
|
||||
const auto f = [=] { _requestIdEmojiList = 0; };
|
||||
_requestIdEmojiList = isGroup
|
||||
? _api.request(
|
||||
MTPaccount_GetDefaultGroupPhotoEmojis()
|
||||
).done(d).fail(f).send()
|
||||
: _api.request(
|
||||
MTPaccount_GetDefaultProfilePhotoEmojis()
|
||||
).done(d).fail(f).send();
|
||||
list.requestId = (type == EmojiListType::Profile)
|
||||
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
|
||||
: (type == EmojiListType::Group)
|
||||
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
|
||||
: send(MTPaccount_GetDefaultBackgroundEmojis());
|
||||
}
|
||||
|
||||
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
|
||||
EmojiListType type) {
|
||||
auto &list = (type == EmojiListType::Group)
|
||||
? _profileEmojiList
|
||||
: _groupEmojiList;
|
||||
if (list.current().empty() && !_requestIdEmojiList) {
|
||||
auto &list = emojiList(type);
|
||||
if (list.list.current().empty() && !list.requestId) {
|
||||
requestEmojiList(type);
|
||||
}
|
||||
return list.value();
|
||||
return list.list.value();
|
||||
}
|
||||
|
||||
// Non-personal photo in case a personal photo is set.
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
enum class EmojiListType {
|
||||
Profile,
|
||||
Group,
|
||||
Background,
|
||||
};
|
||||
|
||||
struct UserPhoto {
|
||||
|
@ -73,6 +74,10 @@ private:
|
|||
Suggestion,
|
||||
Fallback,
|
||||
};
|
||||
struct EmojiListData {
|
||||
rpl::variable<EmojiList> list;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void ready(
|
||||
const FullMsgId &msgId,
|
||||
|
@ -84,6 +89,9 @@ private:
|
|||
UploadType type,
|
||||
Fn<void()> done);
|
||||
|
||||
[[nodiscard]] EmojiListData &emojiList(EmojiListType type);
|
||||
[[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
|
@ -101,9 +109,9 @@ private:
|
|||
not_null<UserData*>,
|
||||
not_null<PhotoData*>> _nonPersonalPhotos;
|
||||
|
||||
mtpRequestId _requestIdEmojiList = 0;
|
||||
rpl::variable<EmojiList> _profileEmojiList;
|
||||
rpl::variable<EmojiList> _groupEmojiList;
|
||||
EmojiListData _profileEmojiList;
|
||||
EmojiListData _groupEmojiList;
|
||||
EmojiListData _backgroundEmojiList;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
|
@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "info/boosts/info_boosts_widget.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
|
@ -105,7 +107,8 @@ public:
|
|||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<uint8> colorIndexValue);
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> backgroundEmojiId);
|
||||
~PreviewWrap();
|
||||
|
||||
private:
|
||||
|
@ -253,7 +256,8 @@ PreviewWrap::PreviewWrap(
|
|||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<uint8> colorIndexValue)
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> backgroundEmojiId)
|
||||
: RpWidget(box)
|
||||
, _box(box)
|
||||
, _peer(peer)
|
||||
|
@ -329,6 +333,10 @@ PreviewWrap::PreviewWrap(
|
|||
_fake->changeColorIndex(index);
|
||||
update();
|
||||
}, lifetime());
|
||||
std::move(backgroundEmojiId) | rpl::start_with_next([=](DocumentId id) {
|
||||
_fake->changeBackgroundEmojiId(id);
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
const auto session = &_history->session();
|
||||
session->data().viewRepaintRequest(
|
||||
|
@ -423,22 +431,28 @@ HistoryView::Context PreviewDelegate::elementContext() {
|
|||
void Set(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex) {
|
||||
const auto was = peer->colorIndex();
|
||||
peer->changeColorIndex(colorIndex);
|
||||
peer->session().changes().peerUpdated(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Color);
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId) {
|
||||
const auto wasIndex = peer->colorIndex();
|
||||
const auto wasEmojiId = peer->backgroundEmojiId();
|
||||
|
||||
const auto setLocal = [=](uint8 index, DocumentId emojiId) {
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
peer->changeColorIndex(index);
|
||||
peer->changeBackgroundEmojiId(emojiId);
|
||||
peer->session().changes().peerUpdated(
|
||||
peer,
|
||||
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
|
||||
};
|
||||
setLocal(colorIndex, backgroundEmojiId);
|
||||
|
||||
const auto done = [=] {
|
||||
show->showToast(peer->isSelf()
|
||||
? tr::lng_settings_color_changed(tr::now)
|
||||
: tr::lng_settings_color_changed_channel(tr::now));
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
peer->changeColorIndex(was);
|
||||
peer->session().changes().peerUpdated(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Color);
|
||||
setLocal(wasIndex, wasEmojiId);
|
||||
show->showToast(error.type());
|
||||
};
|
||||
const auto send = [&](auto &&request) {
|
||||
|
@ -451,14 +465,14 @@ void Set(
|
|||
MTP_flags(
|
||||
MTPaccount_UpdateColor::Flag::f_background_emoji_id),
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(peer->backgroundEmojiId())));
|
||||
MTP_long(backgroundEmojiId)));
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
send(MTPchannels_UpdateColor(
|
||||
MTP_flags(
|
||||
MTPchannels_UpdateColor::Flag::f_background_emoji_id),
|
||||
channel->inputChannel,
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(peer->backgroundEmojiId())));
|
||||
MTP_long(backgroundEmojiId)));
|
||||
} else {
|
||||
Unexpected("Invalid peer type in Set(colorIndex).");
|
||||
}
|
||||
|
@ -468,10 +482,12 @@ void Apply(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId,
|
||||
Fn<void()> close,
|
||||
Fn<void()> cancel) {
|
||||
const auto session = &peer->session();
|
||||
if (peer->colorIndex() == colorIndex) {
|
||||
if (peer->colorIndex() == colorIndex
|
||||
&& peer->backgroundEmojiId() == backgroundEmojiId) {
|
||||
close();
|
||||
} else if (peer->isSelf() && !session->premium()) {
|
||||
Settings::ShowPremiumPromoToast(
|
||||
|
@ -486,7 +502,7 @@ void Apply(
|
|||
u"name_color"_q);
|
||||
cancel();
|
||||
} else if (peer->isSelf()) {
|
||||
Set(show, peer, colorIndex);
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
close();
|
||||
} else {
|
||||
session->api().request(MTPpremium_GetBoostsStatus(
|
||||
|
@ -497,7 +513,7 @@ void Apply(
|
|||
"channel_color_level_min",
|
||||
5);
|
||||
if (data.vlevel().v >= required) {
|
||||
Set(show, peer, colorIndex);
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
@ -635,6 +651,121 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> emojiIdValue,
|
||||
Fn<void(DocumentId)> emojiIdChosen) {
|
||||
const auto &basicSt = st::settingsButtonNoIcon;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto added = st::normalFont->spacew;
|
||||
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
|
||||
const auto noneWidth = added
|
||||
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
|
||||
const auto emojiWidth = added + emojiSize;
|
||||
const auto rightPadding = std::max(noneWidth, emojiWidth)
|
||||
+ basicSt.padding.right();
|
||||
const auto st = parent->lifetime().make_state<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = CreateButton(
|
||||
parent,
|
||||
tr::lng_settings_color_emoji(),
|
||||
*st,
|
||||
{});
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
struct State {
|
||||
Info::Profile::EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId emojiId = 0;
|
||||
uint8 index = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.backgroundEmojiChosen(
|
||||
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
state->index = index;
|
||||
if (state->emoji) {
|
||||
right->update();
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
|
||||
state->emojiId = emojiId;
|
||||
state->emoji = emojiId
|
||||
? session->data().customEmojiManager().create(
|
||||
emojiId,
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
(emojiId ? emojiWidth : noneWidth) + added,
|
||||
right->height());
|
||||
right->update();
|
||||
}, right->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->sizeValue(),
|
||||
right->widthValue()
|
||||
) | rpl::start_with_next([=](QSize outer, int width) {
|
||||
right->resize(width, outer.height());
|
||||
const auto skip = st::settingsButton.padding.right();
|
||||
right->moveToRight(skip - added, 0, outer.width());
|
||||
}, right->lifetime());
|
||||
|
||||
right->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (state->panel.paintBadgeFrame(right)) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(right);
|
||||
const auto height = right->height();
|
||||
if (state->emoji) {
|
||||
const auto colors = style->coloredValues(false, state->index);
|
||||
state->emoji->paint(p, {
|
||||
.textColor = colors.name,
|
||||
.position = QPoint(added, (height - emojiSize) / 2),
|
||||
.internal = {
|
||||
.forceFirstFrame = true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const auto &font = st::normalFont;
|
||||
p.setFont(font);
|
||||
p.setPen(style->windowActiveTextFg());
|
||||
p.drawText(
|
||||
QPoint(added, (height - font->height) / 2 + font->ascent),
|
||||
tr::lng_settings_color_emoji_off(tr::now));
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto customTextColor = [=] {
|
||||
return style->coloredValues(false, state->index).name;
|
||||
};
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.currentBackgroundEmojiId = state->emojiId,
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EditPeerColorBox(
|
||||
|
@ -648,18 +779,21 @@ void EditPeerColorBox(
|
|||
|
||||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
bool changing = false;
|
||||
bool applying = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->index = peer->colorIndex();
|
||||
state->emojiId = peer->backgroundEmojiId();
|
||||
|
||||
box->addRow(object_ptr<PreviewWrap>(
|
||||
box,
|
||||
style,
|
||||
theme,
|
||||
peer,
|
||||
state->index.value()
|
||||
state->index.value(),
|
||||
state->emojiId.value()
|
||||
), {});
|
||||
|
||||
const auto appConfig = &peer->session().account().appConfig();
|
||||
|
@ -691,12 +825,29 @@ void EditPeerColorBox(
|
|||
? tr::lng_settings_color_about()
|
||||
: tr::lng_settings_color_about_channel());
|
||||
|
||||
AddSkip(container, st::settingsColorSampleSkip);
|
||||
|
||||
container->add(CreateEmojiIconButton(
|
||||
container,
|
||||
show,
|
||||
style,
|
||||
state->index.value(),
|
||||
state->emojiId.value(),
|
||||
[=](DocumentId id) { state->emojiId = id; }));
|
||||
|
||||
AddSkip(container, st::settingsColorSampleSkip);
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_settings_color_emoji_about()
|
||||
: tr::lng_settings_color_emoji_about_channel());
|
||||
|
||||
box->addButton(tr::lng_settings_apply(), [=] {
|
||||
if (state->applying) {
|
||||
return;
|
||||
}
|
||||
state->applying = true;
|
||||
Apply(show, peer, state->index.current(), crl::guard(box, [=] {
|
||||
const auto index = state->index.current();
|
||||
const auto emojiId = state->emojiId.current();
|
||||
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
|
||||
box->closeBox();
|
||||
}), crl::guard(box, [=] {
|
||||
state->applying = false;
|
||||
|
|
|
@ -662,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) {
|
|||
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
|
||||
fadeRight: icon {{ "fade_horizontal", windowBg }};
|
||||
}
|
||||
backgroundEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||
padding: margins(7px, 7px, 4px, 0px);
|
||||
}
|
||||
|
||||
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
|
||||
deltat: stickerPanPadding;
|
||||
|
|
|
@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget(
|
|||
, _localSetsManager(
|
||||
std::make_unique<LocalStickersManager>(&session()))
|
||||
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _overBg(st::emojiPanRadius, st().overBg)
|
||||
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
||||
, _picker(this, st())
|
||||
|
@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget(
|
|||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
if (_mode != Mode::RecentReactions) {
|
||||
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
|
||||
setupSearch();
|
||||
}
|
||||
|
||||
|
@ -791,10 +792,12 @@ object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
|
|||
};
|
||||
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
|
||||
.session = &session(),
|
||||
.customTextColor = _customTextColor,
|
||||
.paused = footerPaused,
|
||||
.parent = this,
|
||||
.st = &st(),
|
||||
.features = { .stickersSettings = false },
|
||||
.forceFirstFrame = (_mode == Mode::BackgroundEmoji),
|
||||
});
|
||||
_footer = result;
|
||||
|
||||
|
@ -1027,6 +1030,14 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
|||
if (!id && _mode == Mode::EmojiStatus) {
|
||||
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
|
||||
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
|
||||
} else if (!id && _mode == Mode::BackgroundEmoji) {
|
||||
const auto fakeId = DocumentId(5246772116543512028ULL);
|
||||
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
|
||||
_recent.push_back({
|
||||
.custom = resolveCustomRecent(fakeId),
|
||||
.id = { Ui::Emoji::Find(no) },
|
||||
});
|
||||
_recentCustomIds.emplace(fakeId);
|
||||
} else {
|
||||
_recent.push_back({
|
||||
.custom = resolveCustomRecent(id),
|
||||
|
@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
|||
void EmojiListWidget::validateEmojiPaintContext(
|
||||
const ExpandingContext &context) {
|
||||
auto value = Ui::Text::CustomEmojiPaintContext{
|
||||
.textColor = (_mode == Mode::EmojiStatus
|
||||
.textColor = (_customTextColor
|
||||
? _customTextColor()
|
||||
: (_mode == Mode::EmojiStatus)
|
||||
? anim::color(
|
||||
st::stickerPanPremium1,
|
||||
st::stickerPanPremium2,
|
||||
|
@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext(
|
|||
.scale = context.progress,
|
||||
.paused = On(powerSavingFlag()) || paused(),
|
||||
.scaled = context.expanding,
|
||||
.internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) },
|
||||
};
|
||||
if (!_emojiPaintContext) {
|
||||
_emojiPaintContext = std::make_unique<
|
||||
|
@ -1384,7 +1398,17 @@ void EmojiListWidget::drawRecent(
|
|||
_emojiPaintContext->position = position
|
||||
+ _innerPosition
|
||||
+ _customPosition;
|
||||
const auto isResetIcon = (_mode == Mode::BackgroundEmoji)
|
||||
&& v::is<EmojiPtr>(recent.id.data);
|
||||
if (isResetIcon) {
|
||||
_emojiPaintContext->textColor = st::windowSubTextFg->c;
|
||||
}
|
||||
custom->paint(p, *_emojiPaintContext);
|
||||
if (isResetIcon) {
|
||||
_emojiPaintContext->textColor = _customTextColor
|
||||
? _customTextColor()
|
||||
: st().textFg->c;
|
||||
}
|
||||
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
|
||||
if (_mode == Mode::EmojiStatus) {
|
||||
position += QPoint(
|
||||
|
@ -1629,6 +1653,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
case Mode::TopicIcon:
|
||||
Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
|
||||
break;
|
||||
case Mode::BackgroundEmoji:
|
||||
Settings::ShowPremium(resolved, u"name_color"_q);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1995,7 +2022,10 @@ void EmojiListWidget::refreshCustom() {
|
|||
const auto &sets = owner->stickers().sets();
|
||||
const auto push = [&](uint64 setId, bool installed) {
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend() || it->second->stickers.isEmpty()) {
|
||||
if (it == sets.cend()
|
||||
|| it->second->stickers.isEmpty()
|
||||
|| (_mode == Mode::BackgroundEmoji
|
||||
&& !it->second->textColor())) {
|
||||
return;
|
||||
}
|
||||
const auto canRemove = !!(it->second->flags
|
||||
|
|
|
@ -74,11 +74,13 @@ enum class EmojiListMode {
|
|||
FullReactions,
|
||||
RecentReactions,
|
||||
UserpicBuilder,
|
||||
BackgroundEmoji,
|
||||
};
|
||||
|
||||
struct EmojiListDescriptor {
|
||||
std::shared_ptr<Show> show;
|
||||
EmojiListMode mode = EmojiListMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
std::vector<DocumentId> customRecentList;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
|
@ -386,6 +388,7 @@ private:
|
|||
base::flat_map<
|
||||
DocumentId,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||
Fn<QColor()> _customTextColor;
|
||||
int _customSingleSize = 0;
|
||||
bool _allowWithoutPremium = false;
|
||||
Ui::RoundRect _overBg;
|
||||
|
|
|
@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
|
|||
descriptor.parent,
|
||||
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
|
||||
, _session(descriptor.session)
|
||||
, _paused(descriptor.paused)
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _paused(std::move(descriptor.paused))
|
||||
, _features(descriptor.features)
|
||||
, _iconState([=] { update(); })
|
||||
, _subiconState([=] { update(); })
|
||||
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
|
||||
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) {
|
||||
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
|
||||
, _forceFirstFrame(descriptor.forceFirstFrame) {
|
||||
setMouseTracking(true);
|
||||
|
||||
_iconsLeft = st().iconSkip
|
||||
|
@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache(
|
|||
const auto y = (st().footer - icon.pixh) / 2;
|
||||
if (icon.custom) {
|
||||
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
|
||||
.textColor = st().textFg->c,
|
||||
.textColor = (_customTextColor
|
||||
? _customTextColor()
|
||||
: st().textFg->c),
|
||||
.size = QSize(icon.pixw, icon.pixh),
|
||||
.now = now,
|
||||
.scale = context.progress,
|
||||
.position = { x, y },
|
||||
.paused = paused,
|
||||
.scaled = context.expanding,
|
||||
.internal = { .forceFirstFrame = _forceFirstFrame },
|
||||
});
|
||||
} else if (icon.lottie && icon.lottie->ready()) {
|
||||
const auto frame = icon.lottie->frame();
|
||||
|
@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache(
|
|||
return icons[index];
|
||||
};
|
||||
const auto paintOne = [&](int left, const style::icon *icon) {
|
||||
icon->paint(
|
||||
p,
|
||||
left + (_singleWidth - icon->width()) / 2,
|
||||
(st().footer - icon->height()) / 2,
|
||||
width());
|
||||
left += (_singleWidth - icon->width()) / 2;
|
||||
const auto top = (st().footer - icon->height()) / 2;
|
||||
if (_customTextColor) {
|
||||
icon->paint(p, left, top, width(), _customTextColor());
|
||||
} else {
|
||||
icon->paint(p, left, top, width());
|
||||
}
|
||||
};
|
||||
if (_icons[info.index].setId == AllEmojiSectionSetId()
|
||||
&& info.width > _singleWidth) {
|
||||
|
|
|
@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter {
|
|||
public:
|
||||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
not_null<RpWidget*> parent;
|
||||
const style::EmojiPan *st = nullptr;
|
||||
ComposeFeatures features;
|
||||
bool forceFirstFrame = false;
|
||||
};
|
||||
explicit StickersListFooter(Descriptor &&descriptor);
|
||||
|
||||
|
@ -269,6 +271,7 @@ private:
|
|||
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const Fn<QColor()> _customTextColor;
|
||||
const Fn<bool()> _paused;
|
||||
const ComposeFeatures _features;
|
||||
|
||||
|
@ -303,6 +306,7 @@ private:
|
|||
int _subiconsWidth = 0;
|
||||
bool _subiconsExpanded = false;
|
||||
bool _repaintScheduled = false;
|
||||
bool _forceFirstFrame = false;
|
||||
|
||||
rpl::event_stream<> _openSettingsRequests;
|
||||
rpl::event_stream<uint64> _setChosen;
|
||||
|
|
|
@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector(
|
|||
Mode mode)
|
||||
: TabbedSelector(parent, {
|
||||
.show = std::move(show),
|
||||
.st = (mode == Mode::EmojiStatus
|
||||
.st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji)
|
||||
? st::statusEmojiPan
|
||||
: st::defaultEmojiPan),
|
||||
.level = level,
|
||||
|
@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector(
|
|||
, _features(descriptor.features)
|
||||
, _show(std::move(descriptor.show))
|
||||
, _level(descriptor.level)
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _mode(descriptor.mode)
|
||||
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
|
||||
, _categoriesRounding(
|
||||
|
@ -512,7 +513,10 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
|||
.show = _show,
|
||||
.mode = (_mode == Mode::EmojiStatus
|
||||
? EmojiMode::EmojiStatus
|
||||
: _mode == Mode::BackgroundEmoji
|
||||
? EmojiMode::BackgroundEmoji
|
||||
: EmojiMode::Full),
|
||||
.customTextColor = _customTextColor,
|
||||
.paused = paused,
|
||||
.st = &_st,
|
||||
.features = _features,
|
||||
|
|
|
@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
|
|||
EmojiOnly,
|
||||
MediaEditor,
|
||||
EmojiStatus,
|
||||
BackgroundEmoji,
|
||||
};
|
||||
|
||||
struct TabbedSelectorDescriptor {
|
||||
|
@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor {
|
|||
const style::EmojiPan &st;
|
||||
PauseReason level = {};
|
||||
TabbedSelectorMode mode = TabbedSelectorMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
ComposeFeatures features;
|
||||
};
|
||||
|
||||
|
@ -272,6 +274,7 @@ private:
|
|||
const ComposeFeatures _features;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const PauseReason _level = {};
|
||||
const Fn<QColor()> _customTextColor;
|
||||
|
||||
Mode _mode = Mode::Full;
|
||||
int _roundRadius = 0;
|
||||
|
|
|
@ -53,7 +53,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
|
|||
| (data.is_masks() ? Flag::Masks : Flag())
|
||||
| (data.is_emojis() ? Flag::Emoji : Flag())
|
||||
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
||||
| (data.is_videos() ? Flag::Webm : Flag());
|
||||
| (data.is_videos() ? Flag::Webm : Flag())
|
||||
| (data.is_text_color() ? Flag::TextColor : Flag());
|
||||
}
|
||||
|
||||
StickersSet::StickersSet(
|
||||
|
@ -108,6 +109,10 @@ StickersType StickersSet::type() const {
|
|||
: StickersType::Stickers;
|
||||
}
|
||||
|
||||
bool StickersSet::textColor() const {
|
||||
return flags & StickersSetFlag::TextColor;
|
||||
}
|
||||
|
||||
void StickersSet::setThumbnail(const ImageWithLocation &data) {
|
||||
Data::UpdateCloudFile(
|
||||
_thumbnail,
|
||||
|
|
|
@ -57,6 +57,7 @@ enum class StickersSetFlag {
|
|||
Special = (1 << 7),
|
||||
Webm = (1 << 8),
|
||||
Emoji = (1 << 9),
|
||||
TextColor = (1 << 10),
|
||||
};
|
||||
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||
|
@ -84,6 +85,7 @@ public:
|
|||
[[nodiscard]] MTPInputStickerSet mtpInput() const;
|
||||
[[nodiscard]] StickerSetIdentifier identifier() const;
|
||||
[[nodiscard]] StickersType type() const;
|
||||
[[nodiscard]] bool textColor() const;
|
||||
|
||||
void setThumbnail(const ImageWithLocation &data);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
|
@ -66,26 +68,17 @@ void EmojiStatusPanel::show(
|
|||
not_null<Window::SessionController*> controller,
|
||||
not_null<QWidget*> button,
|
||||
Data::CustomEmojiSizeTag animationSizeTag) {
|
||||
const auto self = controller->session().user();
|
||||
const auto &statuses = controller->session().data().emojiStatuses();
|
||||
const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent);
|
||||
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
|
||||
auto list = statuses.list(Data::EmojiStatuses::Type::Colored);
|
||||
list.insert(begin(list), 0);
|
||||
if (list.size() > kLimitFirstRow) {
|
||||
list.erase(begin(list) + kLimitFirstRow, end(list));
|
||||
}
|
||||
list.reserve(list.size() + recent.size() + other.size() + 1);
|
||||
for (const auto &id : ranges::views::concat(recent, other)) {
|
||||
if (!ranges::contains(list, id)) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(list, self->emojiStatusId())) {
|
||||
list.push_back(self->emojiStatusId());
|
||||
}
|
||||
show({
|
||||
.controller = controller,
|
||||
.button = button,
|
||||
.animationSizeTag = animationSizeTag,
|
||||
});
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::show(Descriptor &&descriptor) {
|
||||
const auto controller = descriptor.controller;
|
||||
if (!_panel) {
|
||||
create(controller);
|
||||
create(descriptor);
|
||||
|
||||
_panel->shownValue(
|
||||
) | rpl::filter([=] {
|
||||
|
@ -98,23 +91,67 @@ void EmojiStatusPanel::show(
|
|||
}
|
||||
}, _panel->lifetime());
|
||||
}
|
||||
const auto button = descriptor.button;
|
||||
if (const auto previous = _panelButton.data()) {
|
||||
if (previous != button) {
|
||||
previous->removeEventFilter(_panel.get());
|
||||
}
|
||||
}
|
||||
_panelButton = button;
|
||||
_animationSizeTag = animationSizeTag;
|
||||
_panel->selector()->provideRecentEmoji(list);
|
||||
_animationSizeTag = descriptor.animationSizeTag;
|
||||
auto list = std::vector<DocumentId>();
|
||||
if (descriptor.backgroundEmojiMode) {
|
||||
controller->session().api().peerPhoto().emojiListValue(
|
||||
Api::PeerPhoto::EmojiListType::Background
|
||||
) | rpl::start_with_next([=](std::vector<DocumentId> &&list) {
|
||||
list.insert(begin(list), 0);
|
||||
if (const auto now = descriptor.currentBackgroundEmojiId) {
|
||||
if (!ranges::contains(list, now)) {
|
||||
list.push_back(now);
|
||||
}
|
||||
}
|
||||
_panel->selector()->provideRecentEmoji(list);
|
||||
}, _panel->lifetime());
|
||||
} else {
|
||||
const auto self = controller->session().user();
|
||||
const auto &statuses = controller->session().data().emojiStatuses();
|
||||
const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent);
|
||||
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
|
||||
auto list = statuses.list(Data::EmojiStatuses::Type::Colored);
|
||||
list.insert(begin(list), 0);
|
||||
if (list.size() > kLimitFirstRow) {
|
||||
list.erase(begin(list) + kLimitFirstRow, end(list));
|
||||
}
|
||||
list.reserve(list.size() + recent.size() + other.size() + 1);
|
||||
for (const auto &id : ranges::views::concat(recent, other)) {
|
||||
if (!ranges::contains(list, id)) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(list, self->emojiStatusId())) {
|
||||
list.push_back(self->emojiStatusId());
|
||||
}
|
||||
_panel->selector()->provideRecentEmoji(list);
|
||||
}
|
||||
const auto parent = _panel->parentWidget();
|
||||
const auto global = button->mapToGlobal(QPoint());
|
||||
const auto local = parent->mapFromGlobal(global);
|
||||
_panel->moveTopRight(
|
||||
local.y() + button->height() - (st::normalFont->height / 2),
|
||||
local.x() + button->width() * 3);
|
||||
if (descriptor.backgroundEmojiMode) {
|
||||
_panel->moveBottomRight(
|
||||
local.y() + (st::normalFont->height / 2),
|
||||
local.x() + button->width() * 3);
|
||||
} else {
|
||||
_panel->moveTopRight(
|
||||
local.y() + button->height() - (st::normalFont->height / 2),
|
||||
local.x() + button->width() * 3);
|
||||
}
|
||||
_panel->toggleAnimated();
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::repaint() {
|
||||
_panel->selector()->update();
|
||||
}
|
||||
|
||||
bool EmojiStatusPanel::paintBadgeFrame(not_null<Ui::RpWidget*> widget) {
|
||||
if (!_animation) {
|
||||
return false;
|
||||
|
@ -125,19 +162,31 @@ bool EmojiStatusPanel::paintBadgeFrame(not_null<Ui::RpWidget*> widget) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::create(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
void EmojiStatusPanel::create(const Descriptor &descriptor) {
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
using Descriptor = ChatHelpers::TabbedSelectorDescriptor;
|
||||
using Mode = ChatHelpers::TabbedSelector::Mode;
|
||||
const auto controller = descriptor.controller;
|
||||
const auto body = controller->window().widget()->bodyWidget();
|
||||
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
body,
|
||||
controller,
|
||||
object_ptr<Selector>(
|
||||
nullptr,
|
||||
controller->uiShow(),
|
||||
Window::GifPauseReason::Layer,
|
||||
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
|
||||
_panel->setDropDown(true);
|
||||
Descriptor{
|
||||
.show = controller->uiShow(),
|
||||
.st = (descriptor.backgroundEmojiMode
|
||||
? st::backgroundEmojiPan
|
||||
: st::statusEmojiPan),
|
||||
.level = Window::GifPauseReason::Layer,
|
||||
.mode = (descriptor.backgroundEmojiMode
|
||||
? Mode::BackgroundEmoji
|
||||
: Mode::EmojiStatus),
|
||||
.customTextColor = descriptor.customTextColor,
|
||||
}));
|
||||
_customTextColor = descriptor.customTextColor;
|
||||
_backgroundEmojiMode = descriptor.backgroundEmojiMode;
|
||||
_panel->setDropDown(!_backgroundEmojiMode);
|
||||
_panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
|
@ -169,34 +218,46 @@ void EmojiStatusPanel::create(
|
|||
return Chosen{ .animation = data.messageSendingFrom };
|
||||
});
|
||||
|
||||
const auto weak = Ui::MakeWeak(_panel.get());
|
||||
const auto accept = [=](Chosen chosen) {
|
||||
Expects(chosen.until != Selector::kPickCustomTimeId);
|
||||
|
||||
// From PickUntilBox is called after EmojiStatusPanel is destroyed!
|
||||
const auto owner = &controller->session().data();
|
||||
if (weak) {
|
||||
if (descriptor.backgroundEmojiMode) {
|
||||
rpl::merge(
|
||||
std::move(statusChosen),
|
||||
std::move(emojiChosen)
|
||||
) | rpl::start_with_next([=](const Chosen &chosen) {
|
||||
const auto owner = &controller->session().data();
|
||||
startAnimation(owner, body, chosen.id, chosen.animation);
|
||||
}
|
||||
owner->emojiStatuses().set(chosen.id, chosen.until);
|
||||
};
|
||||
_backgroundEmojiChosen.fire_copy(chosen.id);
|
||||
_panel->hideAnimated();
|
||||
}, _panel->lifetime());
|
||||
} else {
|
||||
const auto weak = Ui::MakeWeak(_panel.get());
|
||||
const auto accept = [=](Chosen chosen) {
|
||||
Expects(chosen.until != Selector::kPickCustomTimeId);
|
||||
|
||||
rpl::merge(
|
||||
std::move(statusChosen),
|
||||
std::move(emojiChosen)
|
||||
) | rpl::filter([=](const Chosen &chosen) {
|
||||
return filter(controller, chosen.id);
|
||||
}) | rpl::start_with_next([=](const Chosen &chosen) {
|
||||
if (chosen.until == Selector::kPickCustomTimeId) {
|
||||
_panel->hideAnimated();
|
||||
controller->show(Box(PickUntilBox, [=](TimeId seconds) {
|
||||
accept({ chosen.id, base::unixtime::now() + seconds });
|
||||
}));
|
||||
} else {
|
||||
accept(chosen);
|
||||
_panel->hideAnimated();
|
||||
}
|
||||
}, _panel->lifetime());
|
||||
// PickUntilBox calls this after EmojiStatusPanel is destroyed!
|
||||
const auto owner = &controller->session().data();
|
||||
if (weak) {
|
||||
startAnimation(owner, body, chosen.id, chosen.animation);
|
||||
}
|
||||
owner->emojiStatuses().set(chosen.id, chosen.until);
|
||||
};
|
||||
|
||||
rpl::merge(
|
||||
std::move(statusChosen),
|
||||
std::move(emojiChosen)
|
||||
) | rpl::filter([=](const Chosen &chosen) {
|
||||
return filter(controller, chosen.id);
|
||||
}) | rpl::start_with_next([=](const Chosen &chosen) {
|
||||
if (chosen.until == Selector::kPickCustomTimeId) {
|
||||
_panel->hideAnimated();
|
||||
controller->show(Box(PickUntilBox, [=](TimeId seconds) {
|
||||
accept({ chosen.id, base::unixtime::now() + seconds });
|
||||
}));
|
||||
} else {
|
||||
accept(chosen);
|
||||
_panel->hideAnimated();
|
||||
}
|
||||
}, _panel->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
bool EmojiStatusPanel::filter(
|
||||
|
@ -223,13 +284,17 @@ void EmojiStatusPanel::startAnimation(
|
|||
.id = { { statusId } },
|
||||
.flyIcon = from.frame,
|
||||
.flyFrom = body->mapFromGlobal(from.globalStartGeometry),
|
||||
.forceFirstFrame = _backgroundEmojiMode,
|
||||
};
|
||||
const auto color = _customTextColor
|
||||
? _customTextColor
|
||||
: [] { return st::profileVerifiedCheckBg->c; };
|
||||
_animation = std::make_unique<Ui::EmojiFlyAnimation>(
|
||||
body,
|
||||
&owner->reactions(),
|
||||
std::move(args),
|
||||
[=] { _animation->repaint(); },
|
||||
[] { return st::profileVerifiedCheckBg->c; },
|
||||
_customTextColor,
|
||||
_animationSizeTag);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,10 +47,25 @@ public:
|
|||
not_null<QWidget*> button,
|
||||
Data::CustomEmojiSizeTag animationSizeTag = {});
|
||||
|
||||
struct Descriptor {
|
||||
not_null<Window::SessionController*> controller;
|
||||
not_null<QWidget*> button;
|
||||
Data::CustomEmojiSizeTag animationSizeTag = {};
|
||||
DocumentId currentBackgroundEmojiId = 0;
|
||||
Fn<QColor()> customTextColor;
|
||||
bool backgroundEmojiMode = false;
|
||||
};
|
||||
void show(Descriptor &&descriptor);
|
||||
void repaint();
|
||||
|
||||
[[nodiscard]] rpl::producer<DocumentId> backgroundEmojiChosen() const {
|
||||
return _backgroundEmojiChosen.events();
|
||||
}
|
||||
|
||||
bool paintBadgeFrame(not_null<Ui::RpWidget*> widget);
|
||||
|
||||
private:
|
||||
void create(not_null<Window::SessionController*> controller);
|
||||
void create(const Descriptor &descriptor);
|
||||
[[nodiscard]] bool filter(
|
||||
not_null<Window::SessionController*> controller,
|
||||
DocumentId chosenId) const;
|
||||
|
@ -62,10 +77,13 @@ private:
|
|||
Ui::MessageSendingAnimationFrom from);
|
||||
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
|
||||
Fn<QColor()> _customTextColor;
|
||||
Fn<bool(DocumentId)> _chooseFilter;
|
||||
QPointer<QWidget> _panelButton;
|
||||
std::unique_ptr<Ui::EmojiFlyAnimation> _animation;
|
||||
rpl::event_stream<DocumentId> _backgroundEmojiChosen;
|
||||
Data::CustomEmojiSizeTag _animationSizeTag = {};
|
||||
bool _backgroundEmojiMode = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -69,7 +69,8 @@ ReactionFlyAnimation::ReactionFlyAnimation(
|
|||
, _repaint(std::move(repaint))
|
||||
, _flyFrom(args.flyFrom)
|
||||
, _scaleOutDuration(args.scaleOutDuration)
|
||||
, _scaleOutTarget(args.scaleOutTarget) {
|
||||
, _scaleOutTarget(args.scaleOutTarget)
|
||||
, _forceFirstFrame(args.forceFirstFrame) {
|
||||
const auto &list = owner->list(::Data::Reactions::Type::All);
|
||||
auto centerIcon = (DocumentData*)nullptr;
|
||||
auto aroundAnimation = (DocumentData*)nullptr;
|
||||
|
@ -251,6 +252,7 @@ void ReactionFlyAnimation::paintCenterFrame(
|
|||
target.x() + (target.width() - _customSize) / 2,
|
||||
target.y() + (target.height() - _customSize) / 2),
|
||||
.scaled = scaled,
|
||||
.internal = { .forceFirstFrame = _forceFirstFrame },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +280,7 @@ void ReactionFlyAnimation::paintMiniCopies(
|
|||
.size = size,
|
||||
.now = now,
|
||||
.scaled = true,
|
||||
.internal = { .forceFirstFrame = _forceFirstFrame },
|
||||
};
|
||||
for (const auto &mini : _miniCopies) {
|
||||
if (progress >= mini.duration) {
|
||||
|
|
|
@ -31,6 +31,7 @@ struct ReactionFlyAnimationArgs {
|
|||
float64 scaleOutTarget = 0.;
|
||||
float64 miniCopyMultiplier = 1.;
|
||||
bool effectOnly = false;
|
||||
bool forceFirstFrame = false;
|
||||
|
||||
[[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const;
|
||||
};
|
||||
|
@ -42,6 +43,7 @@ struct ReactionFlyCenter {
|
|||
float64 centerSizeMultiplier = 0.;
|
||||
int customSize = 0;
|
||||
int size = 0;
|
||||
bool forceFirstFrame = false;
|
||||
};
|
||||
|
||||
class ReactionFlyAnimation final {
|
||||
|
@ -121,6 +123,7 @@ private:
|
|||
crl::time _scaleOutDuration = 0;
|
||||
float64 _scaleOutTarget = 0.;
|
||||
bool _noEffectScaleStarted = false;
|
||||
bool _forceFirstFrame = false;
|
||||
bool _effectOnly = false;
|
||||
bool _valid = false;
|
||||
|
||||
|
|
Loading…
Reference in a new issue