Show all stickers as emoji after default categories.

This commit is contained in:
John Preston 2022-07-08 19:00:30 +04:00
parent 248e0d502c
commit 0ed434cfaf
13 changed files with 469 additions and 57 deletions

View file

@ -10,11 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/text/custom_emoji_instance.h"
#include "ui/emoji_config.h"
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
#include "lang/lang_keys.h"
#include "layout/layout_position.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "emoji_suggestions_data.h"
#include "emoji_suggestions_helper.h"
#include "main/main_session.h"
@ -88,7 +93,6 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void prepareSection(int &left, int top, int _width, Ui::IconButton *sectionIcon, Section section);
void setActiveSection(Section section);
not_null<EmojiListWidget*> _pan;
@ -96,7 +100,32 @@ private:
};
EmojiListWidget::Footer::Footer(not_null<EmojiListWidget*> parent) : InnerFooter(parent)
struct EmojiListWidget::CustomInstance {
CustomInstance(
std::unique_ptr<Ui::CustomEmoji::Loader> loader,
Fn<void(
not_null<Ui::CustomEmoji::Instance*>,
Ui::CustomEmoji::RepaintRequest)> repaintLater,
Fn<void()> repaint);
Ui::CustomEmoji::Instance emoji;
Ui::CustomEmoji::Object object;
};
EmojiListWidget::CustomInstance::CustomInstance(
std::unique_ptr<Ui::CustomEmoji::Loader> loader,
Fn<void(
not_null<Ui::CustomEmoji::Instance*>,
Ui::CustomEmoji::RepaintRequest)> repaintLater,
Fn<void()> repaint)
: emoji(
Ui::CustomEmoji::Loading(std::move(loader), Ui::CustomEmoji::Preview()),
std::move(repaintLater))
, object(&emoji, std::move(repaint)) {
}
EmojiListWidget::Footer::Footer(not_null<EmojiListWidget*> parent)
: InnerFooter(parent)
, _pan(parent)
, _sections { {
object_ptr<Ui::IconButton>(this, st::emojiCategoryRecent),
@ -389,7 +418,8 @@ EmojiListWidget::EmojiListWidget(
not_null<Window::SessionController*> controller)
: Inner(parent, controller)
, _picker(this)
, _showPickerTimer([=] { showPicker(); }) {
, _showPickerTimer([=] { showPicker(); })
, _repaintTimer([=] { invokeRepaints(); }) {
setMouseTracking(true);
setAttribute(Qt::WA_OpaquePaintEvent);
@ -408,16 +438,112 @@ EmojiListWidget::EmojiListWidget(
) | rpl::start_with_next([=](EmojiPtr emoji) {
colorChosen(emoji);
}, lifetime());
_picker->hidden(
) | rpl::start_with_next([=] {
pickerHidden();
}, lifetime());
controller->session().data().stickers().updated(
) | rpl::start_with_next([=] {
refreshCustom();
resizeToWidth(width());
}, lifetime());
}
EmojiListWidget::~EmojiListWidget() {
base::take(_instances);
base::take(_repaints);
}
void EmojiListWidget::repaintLater(
uint64 setId,
Ui::CustomEmoji::RepaintRequest request) {
if (_instances.empty()) {
return;
}
auto &repaint = _repaints[request.duration];
if (repaint.when < request.when) {
repaint.when = request.when;
}
repaint.ids.emplace(setId);
scheduleRepaintTimer();
}
void EmojiListWidget::scheduleRepaintTimer() {
if (_repaintTimerScheduled) {
return;
}
_repaintTimerScheduled = true;
Ui::PostponeCall(this, [=] {
_repaintTimerScheduled = false;
auto next = crl::time();
for (const auto &[duration, bunch] : _repaints) {
if (!next || next > bunch.when) {
next = bunch.when;
}
}
if (next && (!_repaintNext || _repaintNext > next)) {
const auto now = crl::now();
if (now >= next) {
_repaintNext = 0;
_repaintTimer.cancel();
invokeRepaints();
} else {
_repaintNext = next;
_repaintTimer.callOnce(next - now);
}
}
});
}
void EmojiListWidget::invokeRepaints() {
_repaintNext = 0;
auto ids = base::flat_set<uint64>();
const auto now = crl::now();
for (auto i = begin(_repaints); i != end(_repaints);) {
if (i->second.when > now) {
++i;
continue;
}
if (ids.empty()) {
ids = std::move(i->second.ids);
} else {
for (const auto id : i->second.ids) {
ids.emplace(id);
}
}
i = _repaints.erase(i);
}
repaintCustom([&](uint64 id) { return ids.contains(id); });
scheduleRepaintTimer();
}
template <typename CheckId>
void EmojiListWidget::repaintCustom(CheckId checkId) {
enumerateSections([&](const SectionInfo &info) {
if (info.section >= kEmojiSectionCount
&& checkId(_custom[info.section - kEmojiSectionCount].id)) {
update(
0,
info.rowsTop,
width(),
info.rowsBottom - info.rowsTop);
}
return true;
});
}
rpl::producer<EmojiPtr> EmojiListWidget::chosen() const {
return _chosen.events();
}
auto EmojiListWidget::customChosen() const
-> rpl::producer<TabbedSelector::FileChosen> {
return _customChosen.events();
}
void EmojiListWidget::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
@ -425,6 +551,28 @@ void EmojiListWidget::visibleTopBottomUpdated(
if (_footer) {
_footer->setCurrentSectionIcon(currentSection(visibleTop));
}
unloadNotSeenCustom(visibleTop, visibleBottom);
}
void EmojiListWidget::unloadNotSeenCustom(
int visibleTop,
int visibleBottom) {
enumerateSections([&](const SectionInfo &info) {
if (info.section < kEmojiSectionCount
|| (info.rowsBottom > visibleTop
&& info.rowsTop < visibleBottom)) {
return true;
}
auto &custom = _custom[info.section - kEmojiSectionCount];
if (!custom.painted) {
return true;
}
custom.painted = false;
for (const auto &single : custom.list) {
single.instance->object.unload();
}
return true;
});
}
object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
@ -438,26 +586,41 @@ template <typename Callback>
bool EmojiListWidget::enumerateSections(Callback callback) const {
Expects(_columnCount > 0);
auto i = 0;
auto info = SectionInfo();
for (auto i = 0; i != kEmojiSectionCount; ++i) {
info.section = i;
info.count = _counts[i];
info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0);
const auto next = [&] {
info.rowsCount = (info.count + _columnCount - 1) / _columnCount;
info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader);
info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height();
if (!callback(info)) {
return false;
}
info.top = info.rowsBottom;
return true;
};
for (; i != kEmojiSectionCount; ++i) {
info.section = i;
info.count = _counts[i];
if (!next()) {
return false;
}
}
for (auto &section : _custom) {
info.section = i++;
info.count = int(section.list.size());
if (!next()) {
return false;
}
}
return true;
}
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const {
Expects(section >= 0 && section < kEmojiSectionCount);
Expects(section >= 0 && section < sectionsCount());
auto result = SectionInfo();
enumerateSections([searchForSection = section, &result](const SectionInfo &info) {
if (info.section == searchForSection) {
enumerateSections([&](const SectionInfo &info) {
if (info.section == section) {
result = info;
return false;
}
@ -466,10 +629,12 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const {
return result;
}
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) const {
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(
int yOffset) const {
auto result = SectionInfo();
enumerateSections([&result, yOffset](const SectionInfo &info) {
if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) {
const auto count = sectionsCount();
enumerateSections([&result, count, yOffset](const SectionInfo &info) {
if (yOffset < info.rowsBottom || info.section == count - 1) {
result = info;
return false;
}
@ -478,8 +643,14 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) c
return result;
}
int EmojiListWidget::sectionsCount() const {
return kEmojiSectionCount + int(_custom.size());
}
int EmojiListWidget::countDesiredHeight(int newWidth) {
auto fullWidth = (st::roundRadiusSmall + newWidth + st::emojiScroll.width);
const auto fullWidth = st::roundRadiusSmall
+ newWidth
+ st::emojiScroll.width;
_columnCount = std::max(
(fullWidth - st::emojiPadding * 2) / st::emojiPanDesiredSize,
1);
@ -491,13 +662,13 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
_rowsLeft -= st::roundRadiusSmall;
_singleSize = QSize(singleWidth, singleWidth - 4 * st::lineWidth);
_picker->setSingleSize(_singleSize);
return sectionInfo(kEmojiSectionCount - 1).rowsBottom + st::emojiPanPadding;
return sectionInfo(sectionsCount() - 1).rowsBottom + st::emojiPanPadding;
}
void EmojiListWidget::ensureLoaded(int section) {
Expects(section >= 0 && section < kEmojiSectionCount);
Expects(section >= 0 && section < sectionsCount());
if (!_emoji[section].isEmpty()) {
if (section >= kEmojiSectionCount || !_emoji[section].empty()) {
return;
}
_emoji[section] = (static_cast<Section>(section) == Section::Recent)
@ -534,7 +705,10 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
toColumn = _columnCount - toColumn;
}
enumerateSections([this, &p, r, fromColumn, toColumn](const SectionInfo &info) {
const auto paused = controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
const auto now = crl::now();
enumerateSections([&](const SectionInfo &info) {
if (r.top() >= info.rowsBottom) {
return true;
} else if (r.top() + r.height() <= info.top) {
@ -543,7 +717,14 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
if (info.section > 0 && r.top() < info.rowsTop) {
p.setFont(st::emojiPanHeaderFont);
p.setPen(st::emojiPanHeaderFg);
p.drawTextLeft(st::emojiPanHeaderLeft - st::roundRadiusSmall, info.top + st::emojiPanHeaderTop, width(), ChatHelpers::EmojiCategoryTitle(info.section)(tr::now));
const auto text = (info.section < kEmojiSectionCount)
? ChatHelpers::EmojiCategoryTitle(info.section)(tr::now)
: _custom[info.section - kEmojiSectionCount].title;
p.drawTextLeft(
st::emojiPanHeaderLeft - st::roundRadiusSmall,
info.top + st::emojiPanHeaderTop,
width(),
text);
}
if (r.top() + r.height() > info.rowsTop) {
ensureLoaded(info.section);
@ -567,12 +748,12 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners);
}
Ui::Emoji::Draw(
p,
_emoji[info.section][index],
_esize,
w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2,
w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2);
if (info.section < kEmojiSectionCount) {
drawEmoji(p, w, info.section, index);
} else {
const auto set = info.section - kEmojiSectionCount;
drawCustom(p, w, now, paused, set, index);
}
}
}
}
@ -580,6 +761,38 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
});
}
void EmojiListWidget::drawEmoji(
QPainter &p,
QPoint position,
int section,
int index) {
const auto size = (_esize / cIntRetinaFactor());
Ui::Emoji::Draw(
p,
_emoji[section][index],
_esize,
position.x() + (_singleSize.width() - size) / 2,
position.y() + (_singleSize.height() - size) / 2);
}
void EmojiListWidget::drawCustom(
QPainter &p,
QPoint position,
crl::time now,
bool paused,
int set,
int index) {
const auto size = (_esize / cIntRetinaFactor());
_custom[set].painted = true;
_custom[set].list[index].instance->object.paint(
p,
position.x() + (_singleSize.width() - size) / 2,
position.y() + (_singleSize.height() - size) / 2,
now,
st::windowBgRipple->c,
paused);
}
bool EmojiListWidget::checkPickerHide() {
if (!_picker->isHidden() && _pickerSel >= 0) {
_picker->hideAnimated();
@ -644,18 +857,21 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
_picker->hide();
}
if (_selected < 0 || _selected != pressed) return;
if (_selected >= Layout::PositionToIndex(kEmojiSectionCount, 0)) {
if (_selected < 0 || _selected != pressed) {
return;
}
const auto &[section, sel] = Layout::IndexToPosition(_selected);
if (sel < _emoji[section].size()) {
auto emoji = _emoji[section][sel];
if (emoji->hasVariants() && !_picker->isHidden()) return;
if (section < kEmojiSectionCount && sel < _emoji[section].size()) {
const auto emoji = _emoji[section][sel];
if (emoji->hasVariants() && !_picker->isHidden()) {
return;
}
selectEmoji(emoji);
} else if (section >= kEmojiSectionCount
&& sel < _custom[section - kEmojiSectionCount].list.size()) {
auto &set = _custom[section - kEmojiSectionCount];
selectCustom(set.list[sel].document);
}
}
@ -664,6 +880,10 @@ void EmojiListWidget::selectEmoji(EmojiPtr emoji) {
_chosen.fire_copy(emoji);
}
void EmojiListWidget::selectCustom(not_null<DocumentData*> document) {
_customChosen.fire({ .document = document });
}
void EmojiListWidget::showPicker() {
if (_pickerSel < 0) return;
@ -694,7 +914,7 @@ void EmojiListWidget::pickerHidden() {
updateSelected();
}
QRect EmojiListWidget::emojiRect(int section, int sel) {
QRect EmojiListWidget::emojiRect(int section, int sel) const {
Expects(_columnCount > 0);
auto info = sectionInfo(section);
@ -796,9 +1016,82 @@ void EmojiListWidget::refreshRecent() {
clearSelection();
_emoji[0] = Core::App().settings().recentEmojiSection();
_counts[0] = _emoji[0].size();
refreshCustom();
resizeToWidth(width());
}
void EmojiListWidget::refreshCustom() {
auto searchFromIndex = 0;
auto old = base::take(_custom);
const auto owner = &controller()->session().data();
const auto &order = owner->stickers().setsOrder();
const auto &sets = owner->stickers().sets();
for (const auto setId : order) {
auto it = sets.find(setId);
if (it == sets.cend() || it->second->stickers.isEmpty()) {
continue;
}
const auto &list = it->second->stickers;
const auto i = ranges::find(old, setId, &CustomSet::id);
if (i != end(old)) {
const auto valid = [&] {
const auto count = int(list.size());
if (i->list.size() != count) {
return false;
}
for (auto k = 0; k != count; ++k) {
if (i->list[k].document != list[k]) {
return false;
}
}
return true;
}();
if (valid) {
_custom.push_back(base::take(*i));
continue;
}
}
auto set = std::vector<CustomOne>();
set.reserve(list.size());
for (const auto document : list) {
if (document->sticker()) {
auto i = _instances.find(document->id);
if (i == end(_instances)) {
using Loading = Ui::CustomEmoji::Loading;
auto loader = owner->customEmojiManager().createLoader(
document,
Data::CustomEmojiManager::SizeTag::Large);
const auto repaintDelayed = [=](
not_null<Ui::CustomEmoji::Instance*> instance,
Ui::CustomEmoji::RepaintRequest request) {
repaintLater(setId, request);
};
const auto repaintNow = [=] {
repaintCustom([&](uint64 id) {
return id == setId;
});
};
i = _instances.emplace(
document->id,
std::make_unique<CustomInstance>(
std::move(loader),
std::move(repaintDelayed),
std::move(repaintNow))).first;
}
set.push_back({
.instance = i->second.get(),
.document = document,
});
}
}
_custom.push_back({
.id = setId,
.title = it->second->title,
.list = std::move(set),
});
}
}
bool EmojiListWidget::eventHook(QEvent *e) {
if (e->type() == QEvent::ParentChange) {
if (_picker->parentWidget() != parentWidget()) {
@ -819,7 +1112,7 @@ void EmojiListWidget::updateSelected() {
auto sx = (rtl() ? width() - p.x() : p.x()) - _rowsLeft;
if (sx >= 0 && sx < _columnCount * _singleSize.width()) {
newSelected = qFloor((p.y() - info.rowsTop) / _singleSize.height()) * _columnCount + qFloor(sx / _singleSize.width());
if (newSelected >= _emoji[info.section].size()) {
if (newSelected >= info.count) {
newSelected = -1;
} else {
newSelected += Layout::PositionToIndex(info.section, 0);

View file

@ -16,11 +16,14 @@ template <typename ...Tags>
struct phrase;
} // namespace tr
namespace Ui {
namespace Emoji {
namespace Ui::Emoji {
enum class Section;
} // namespace Emoji
} // namespace Ui
} // namespace Ui::Emoji
namespace Ui::CustomEmoji {
class Instance;
struct RepaintRequest;
} // namespace Ui::CustomEmoji
namespace Window {
class SessionController;
@ -39,6 +42,7 @@ public:
EmojiListWidget(
QWidget *parent,
not_null<Window::SessionController*> controller);
~EmojiListWidget();
using Section = Ui::Emoji::Section;
@ -47,14 +51,16 @@ public:
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
void showEmojiSection(Section section);
Section currentSection(int yOffset) const;
[[nodiscard]] Section currentSection(int yOffset) const;
// Ui::AbstractTooltipShower interface.
QString tooltipText() const override;
QPoint tooltipPos() const override;
bool tooltipWindowActive() const override;
rpl::producer<EmojiPtr> chosen() const;
[[nodiscard]] rpl::producer<EmojiPtr> chosen() const;
[[nodiscard]] auto customChosen() const
-> rpl::producer<TabbedSelector::FileChosen>;
protected:
void visibleTopBottomUpdated(
@ -85,29 +91,69 @@ private:
int rowsTop = 0;
int rowsBottom = 0;
};
struct CustomInstance;
struct CustomOne {
not_null<CustomInstance*> instance;
not_null<DocumentData*> document;
};
struct CustomSet {
uint64 id = 0;
QString title;
std::vector<CustomOne> list;
bool painted = false;
};
struct RepaintSet {
base::flat_set<uint64> ids;
crl::time when = 0;
};
template <typename Callback>
bool enumerateSections(Callback callback) const;
SectionInfo sectionInfo(int section) const;
SectionInfo sectionInfoByOffset(int yOffset) const;
[[nodiscard]] SectionInfo sectionInfo(int section) const;
[[nodiscard]] SectionInfo sectionInfoByOffset(int yOffset) const;
[[nodiscard]] int sectionsCount() const;
void showPicker();
void pickerHidden();
void colorChosen(EmojiPtr emoji);
bool checkPickerHide();
void refreshCustom();
void unloadNotSeenCustom(int visibleTop, int visibleBottom);
void ensureLoaded(int section);
void updateSelected();
void setSelected(int newSelected);
void selectEmoji(EmojiPtr emoji);
void selectCustom(not_null<DocumentData*> document);
void drawEmoji(
QPainter &p,
QPoint position,
int section,
int index);
void drawCustom(
QPainter &p,
QPoint position,
crl::time now,
bool paused,
int set,
int index);
[[nodiscard]] QRect emojiRect(int section, int sel) const;
QRect emojiRect(int section, int sel);
void repaintLater(
uint64 setId,
Ui::CustomEmoji::RepaintRequest request);
template <typename CheckId>
void repaintCustom(CheckId checkId);
void scheduleRepaintTimer();
void invokeRepaints();
Footer *_footer = nullptr;
int _counts[kEmojiSectionCount];
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom;
base::flat_map<uint64, std::unique_ptr<CustomInstance>> _instances;
int _rowsLeft = 0;
int _columnCount = 1;
@ -122,7 +168,13 @@ private:
object_ptr<EmojiColorPicker> _picker;
base::Timer _showPickerTimer;
base::flat_map<crl::time, RepaintSet> _repaints;
bool _repaintTimerScheduled = false;
base::Timer _repaintTimer;
crl::time _repaintNext = 0;
rpl::event_stream<EmojiPtr> _chosen;
rpl::event_stream<TabbedSelector::FileChosen> _customChosen;
};

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "window/window_session_controller.h"
@ -77,14 +78,18 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
i = all.erase(i);
continue;
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
if (!_session->premium()) {
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
const auto emoji = Data::ParseCustomEmojiData(data);
if (emoji.selfId != id) {
i = all.erase(i);
continue;
}
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
if (Data::ParseCustomEmojiData(data).selfId != id) {
i = all.erase(i);
continue;
if (!_session->premium()) {
const auto document = _session->data().document(emoji.id);
if (document->isPremiumSticker()) {
i = all.erase(i);
continue;
}
}
}
++i;

View file

@ -462,7 +462,11 @@ rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
return emoji()->chosen();
}
rpl::producer<TabbedSelector::FileChosen> TabbedSelector::fileChosen() const {
auto TabbedSelector::customEmojiChosen() const -> rpl::producer<FileChosen> {
return emoji()->customChosen();
}
auto TabbedSelector::fileChosen() const -> rpl::producer<FileChosen> {
auto never = rpl::never<TabbedSelector::FileChosen>(
) | rpl::type_erased();
return rpl::merge(

View file

@ -83,6 +83,7 @@ public:
Main::Session &session() const;
rpl::producer<EmojiPtr> emojiChosen() const;
rpl::producer<FileChosen> customEmojiChosen() const;
rpl::producer<FileChosen> fileChosen() const;
rpl::producer<PhotoChosen> photoChosen() const;
rpl::producer<InlineChosen> inlineResultChosen() const;

View file

@ -708,7 +708,7 @@ void DocumentData::automaticLoadSettingsChanged() {
return;
}
_loader = nullptr;
_flags &= ~Flag::DownloadCancelled;
resetCancelled();
}
void DocumentData::finishLoad() {
@ -867,7 +867,7 @@ void DocumentData::save(
cancel();
}
}
_flags &= ~Flag::DownloadCancelled;
resetCancelled();
if (_loader) {
if (fromCloud == LoadFromCloudOrLocal) {
@ -992,6 +992,10 @@ bool DocumentData::cancelled() const {
return (_flags & Flag::DownloadCancelled);
}
void DocumentData::resetCancelled() {
_flags &= ~Flag::DownloadCancelled;
}
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
auto bitsCount = static_cast<int>(encoded5bit.size() * 8);
auto valuesCount = bitsCount / 5;

View file

@ -115,6 +115,7 @@ public:
bool autoLoading = false);
void cancel();
[[nodiscard]] bool cancelled() const;
void resetCancelled();
[[nodiscard]] float64 progress() const;
[[nodiscard]] int64 loadOffset() const;
[[nodiscard]] bool uploading() const;

View file

@ -163,6 +163,7 @@ void CustomEmojiLoader::load(Fn<void(LoadResult)> loaded) {
.media = load->document->createMediaView(),
.loaded = std::move(loaded),
});
load->process->media->owner()->resetCancelled();
load->process->media->checkStickerLarge();
if (load->process->media->loaded()) {
check();
@ -399,6 +400,12 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
std::move(update));
}
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
not_null<DocumentData*> document,
SizeTag tag) {
return std::make_unique<CustomEmojiLoader>(document, tag);
}
void CustomEmojiManager::request() {
auto ids = QVector<MTPlong>();
ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size())));

View file

@ -42,6 +42,10 @@ public:
QStringView data,
Fn<void()> update);
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
not_null<DocumentData*> document,
SizeTag tag);
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] Session &owner() const;

View file

@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_group_call.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_message.h"
@ -1051,6 +1052,13 @@ void HistoryWidget::initTabbedSelector() {
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
}, lifetime());
selector->customEmojiChosen(
) | rpl::filter([=] {
return !isHidden() && !_field->isHidden();
}) | rpl::start_with_next([=](Selector::FileChosen data) {
Data::InsertCustomEmoji(_field.data(), data.document);
}, lifetime());
selector->fileChosen(
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
controller()->sendingAnimation().appendSending(

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_web_page.h"
#include "storage/storage_account.h"
#include "apiwrap.h"
@ -1828,6 +1829,12 @@ void ComposeControls::initTabbedSelector() {
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
}, wrap->lifetime());
using FileChosen = ChatHelpers::TabbedSelector::FileChosen;
selector->customEmojiChosen(
) | rpl::start_with_next([=](FileChosen data) {
Data::InsertCustomEmoji(_field, data.document);
}, wrap->lifetime());
selector->fileChosen(
) | rpl::start_to_stream(_fileChosen, wrap->lifetime());

View file

@ -21,6 +21,7 @@ constexpr auto kMaxSize = 128;
constexpr auto kMaxFrames = 512;
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
constexpr auto kCacheVersion = 1;
constexpr auto kPreloadFrames = 3;
struct CacheHeader {
int version = 0;
@ -377,6 +378,8 @@ Renderer::Renderer(RendererDescriptor &&descriptor)
});
}
Renderer::~Renderer() = default;
void Renderer::frameReady(
std::unique_ptr<Ui::FrameGenerator> generator,
crl::time duration,
@ -390,24 +393,35 @@ void Renderer::frameReady(
_cache.reserve(count);
}
}
const auto explicitRepaint = (_cache.frames() == _cache.currentFrame());
const auto current = _cache.currentFrame();
const auto total = _cache.frames();
const auto explicitRepaint = (current == total);
_cache.add(duration, frame);
if (explicitRepaint && _repaint) {
_repaint();
}
if (!duration) {
finish();
return;
} else if (current + kPreloadFrames > total) {
renderNext(std::move(generator), std::move(frame));
} else {
_generator = std::move(generator);
_storage = std::move(frame);
}
}
void Renderer::renderNext(
std::unique_ptr<Ui::FrameGenerator> generator,
QImage storage) {
const auto size = _cache.size();
const auto guard = base::make_weak(this);
crl::async([
=,
frame = std::move(frame),
storage = std::move(storage),
generator = std::move(generator)
]() mutable {
auto rendered = generator->renderNext(
std::move(frame),
std::move(storage),
QSize(size, size) * style::DevicePixelRatio(),
Qt::KeepAspectRatio);
crl::on_main(guard, [
@ -432,7 +446,13 @@ void Renderer::finish() {
}
PaintFrameResult Renderer::paint(QPainter &p, int x, int y, crl::time now) {
return _cache.paintCurrentFrame(p, x, y, now);
const auto result = _cache.paintCurrentFrame(p, x, y, now);
if (_generator
&& (!result.painted
|| _cache.currentFrame() + kPreloadFrames >= _cache.frames())) {
renderNext(std::move(_generator), std::move(_storage));
}
return result;
}
std::optional<Cached> Renderer::ready(const QString &entityData) {

View file

@ -136,7 +136,7 @@ struct RendererDescriptor {
class Renderer final : public base::has_weak_ptr {
public:
explicit Renderer(RendererDescriptor &&descriptor);
virtual ~Renderer() = default;
virtual ~Renderer();
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now);
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
@ -152,10 +152,14 @@ private:
std::unique_ptr<Ui::FrameGenerator> generator,
crl::time duration,
QImage frame);
void renderNext(
std::unique_ptr<Ui::FrameGenerator> generator,
QImage storage);
void finish();
Cache _cache;
std::unique_ptr<Ui::FrameGenerator> _generator;
QImage _storage;
Fn<void(QByteArray)> _put;
Fn<void()> _repaint;
Fn<std::unique_ptr<Loader>()> _loader;
@ -208,6 +212,8 @@ public:
Instance(
Loading loading,
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater);
Instance(const Instance&) = delete;
Instance &operator=(const Instance&) = delete;
[[nodiscard]] QString entityData() const;
void paint(