mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Show all stickers as emoji after default categories.
This commit is contained in:
parent
248e0d502c
commit
0ed434cfaf
13 changed files with 469 additions and 57 deletions
|
@ -10,11 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/text/custom_emoji_instance.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "ui/cached_round_corners.h"
|
#include "ui/cached_round_corners.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "layout/layout_position.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_data.h"
|
||||||
#include "emoji_suggestions_helper.h"
|
#include "emoji_suggestions_helper.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -88,7 +93,6 @@ protected:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void prepareSection(int &left, int top, int _width, Ui::IconButton *sectionIcon, Section section);
|
|
||||||
void setActiveSection(Section section);
|
void setActiveSection(Section section);
|
||||||
|
|
||||||
not_null<EmojiListWidget*> _pan;
|
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)
|
, _pan(parent)
|
||||||
, _sections { {
|
, _sections { {
|
||||||
object_ptr<Ui::IconButton>(this, st::emojiCategoryRecent),
|
object_ptr<Ui::IconButton>(this, st::emojiCategoryRecent),
|
||||||
|
@ -389,7 +418,8 @@ EmojiListWidget::EmojiListWidget(
|
||||||
not_null<Window::SessionController*> controller)
|
not_null<Window::SessionController*> controller)
|
||||||
: Inner(parent, controller)
|
: Inner(parent, controller)
|
||||||
, _picker(this)
|
, _picker(this)
|
||||||
, _showPickerTimer([=] { showPicker(); }) {
|
, _showPickerTimer([=] { showPicker(); })
|
||||||
|
, _repaintTimer([=] { invokeRepaints(); }) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
@ -408,16 +438,112 @@ EmojiListWidget::EmojiListWidget(
|
||||||
) | rpl::start_with_next([=](EmojiPtr emoji) {
|
) | rpl::start_with_next([=](EmojiPtr emoji) {
|
||||||
colorChosen(emoji);
|
colorChosen(emoji);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_picker->hidden(
|
_picker->hidden(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
pickerHidden();
|
pickerHidden();
|
||||||
}, lifetime());
|
}, 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 {
|
rpl::producer<EmojiPtr> EmojiListWidget::chosen() const {
|
||||||
return _chosen.events();
|
return _chosen.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto EmojiListWidget::customChosen() const
|
||||||
|
-> rpl::producer<TabbedSelector::FileChosen> {
|
||||||
|
return _customChosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
void EmojiListWidget::visibleTopBottomUpdated(
|
void EmojiListWidget::visibleTopBottomUpdated(
|
||||||
int visibleTop,
|
int visibleTop,
|
||||||
int visibleBottom) {
|
int visibleBottom) {
|
||||||
|
@ -425,6 +551,28 @@ void EmojiListWidget::visibleTopBottomUpdated(
|
||||||
if (_footer) {
|
if (_footer) {
|
||||||
_footer->setCurrentSectionIcon(currentSection(visibleTop));
|
_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() {
|
object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
|
||||||
|
@ -438,26 +586,41 @@ template <typename Callback>
|
||||||
bool EmojiListWidget::enumerateSections(Callback callback) const {
|
bool EmojiListWidget::enumerateSections(Callback callback) const {
|
||||||
Expects(_columnCount > 0);
|
Expects(_columnCount > 0);
|
||||||
|
|
||||||
|
auto i = 0;
|
||||||
auto info = SectionInfo();
|
auto info = SectionInfo();
|
||||||
for (auto i = 0; i != kEmojiSectionCount; ++i) {
|
const auto next = [&] {
|
||||||
info.section = i;
|
info.rowsCount = (info.count + _columnCount - 1) / _columnCount;
|
||||||
info.count = _counts[i];
|
|
||||||
info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0);
|
|
||||||
info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader);
|
info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader);
|
||||||
info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height();
|
info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height();
|
||||||
if (!callback(info)) {
|
if (!callback(info)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
info.top = info.rowsBottom;
|
info.top = info.rowsBottom;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
for (; i != kEmojiSectionCount; ++i) {
|
||||||
|
info.section = i;
|
||||||
|
info.count = _counts[i];
|
||||||
|
if (!next()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto §ion : _custom) {
|
||||||
|
info.section = i++;
|
||||||
|
info.count = int(section.list.size());
|
||||||
|
if (!next()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const {
|
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const {
|
||||||
Expects(section >= 0 && section < kEmojiSectionCount);
|
Expects(section >= 0 && section < sectionsCount());
|
||||||
|
|
||||||
auto result = SectionInfo();
|
auto result = SectionInfo();
|
||||||
enumerateSections([searchForSection = section, &result](const SectionInfo &info) {
|
enumerateSections([&](const SectionInfo &info) {
|
||||||
if (info.section == searchForSection) {
|
if (info.section == section) {
|
||||||
result = info;
|
result = info;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -466,10 +629,12 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) const {
|
EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(
|
||||||
|
int yOffset) const {
|
||||||
auto result = SectionInfo();
|
auto result = SectionInfo();
|
||||||
enumerateSections([&result, yOffset](const SectionInfo &info) {
|
const auto count = sectionsCount();
|
||||||
if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) {
|
enumerateSections([&result, count, yOffset](const SectionInfo &info) {
|
||||||
|
if (yOffset < info.rowsBottom || info.section == count - 1) {
|
||||||
result = info;
|
result = info;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -478,8 +643,14 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) c
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int EmojiListWidget::sectionsCount() const {
|
||||||
|
return kEmojiSectionCount + int(_custom.size());
|
||||||
|
}
|
||||||
|
|
||||||
int EmojiListWidget::countDesiredHeight(int newWidth) {
|
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(
|
_columnCount = std::max(
|
||||||
(fullWidth - st::emojiPadding * 2) / st::emojiPanDesiredSize,
|
(fullWidth - st::emojiPadding * 2) / st::emojiPanDesiredSize,
|
||||||
1);
|
1);
|
||||||
|
@ -491,13 +662,13 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
|
||||||
_rowsLeft -= st::roundRadiusSmall;
|
_rowsLeft -= st::roundRadiusSmall;
|
||||||
_singleSize = QSize(singleWidth, singleWidth - 4 * st::lineWidth);
|
_singleSize = QSize(singleWidth, singleWidth - 4 * st::lineWidth);
|
||||||
_picker->setSingleSize(_singleSize);
|
_picker->setSingleSize(_singleSize);
|
||||||
return sectionInfo(kEmojiSectionCount - 1).rowsBottom + st::emojiPanPadding;
|
return sectionInfo(sectionsCount() - 1).rowsBottom + st::emojiPanPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiListWidget::ensureLoaded(int section) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
_emoji[section] = (static_cast<Section>(section) == Section::Recent)
|
_emoji[section] = (static_cast<Section>(section) == Section::Recent)
|
||||||
|
@ -534,7 +705,10 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||||
toColumn = _columnCount - toColumn;
|
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) {
|
if (r.top() >= info.rowsBottom) {
|
||||||
return true;
|
return true;
|
||||||
} else if (r.top() + r.height() <= info.top) {
|
} 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) {
|
if (info.section > 0 && r.top() < info.rowsTop) {
|
||||||
p.setFont(st::emojiPanHeaderFont);
|
p.setFont(st::emojiPanHeaderFont);
|
||||||
p.setPen(st::emojiPanHeaderFg);
|
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) {
|
if (r.top() + r.height() > info.rowsTop) {
|
||||||
ensureLoaded(info.section);
|
ensureLoaded(info.section);
|
||||||
|
@ -567,12 +748,12 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||||
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
|
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
|
||||||
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners);
|
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners);
|
||||||
}
|
}
|
||||||
Ui::Emoji::Draw(
|
if (info.section < kEmojiSectionCount) {
|
||||||
p,
|
drawEmoji(p, w, info.section, index);
|
||||||
_emoji[info.section][index],
|
} else {
|
||||||
_esize,
|
const auto set = info.section - kEmojiSectionCount;
|
||||||
w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2,
|
drawCustom(p, w, now, paused, set, index);
|
||||||
w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
bool EmojiListWidget::checkPickerHide() {
|
||||||
if (!_picker->isHidden() && _pickerSel >= 0) {
|
if (!_picker->isHidden() && _pickerSel >= 0) {
|
||||||
_picker->hideAnimated();
|
_picker->hideAnimated();
|
||||||
|
@ -644,18 +857,21 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
_picker->hide();
|
_picker->hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_selected < 0 || _selected != pressed) return;
|
if (_selected < 0 || _selected != pressed) {
|
||||||
|
|
||||||
if (_selected >= Layout::PositionToIndex(kEmojiSectionCount, 0)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &[section, sel] = Layout::IndexToPosition(_selected);
|
const auto &[section, sel] = Layout::IndexToPosition(_selected);
|
||||||
if (sel < _emoji[section].size()) {
|
if (section < kEmojiSectionCount && sel < _emoji[section].size()) {
|
||||||
auto emoji = _emoji[section][sel];
|
const auto emoji = _emoji[section][sel];
|
||||||
if (emoji->hasVariants() && !_picker->isHidden()) return;
|
if (emoji->hasVariants() && !_picker->isHidden()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectEmoji(emoji);
|
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);
|
_chosen.fire_copy(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmojiListWidget::selectCustom(not_null<DocumentData*> document) {
|
||||||
|
_customChosen.fire({ .document = document });
|
||||||
|
}
|
||||||
|
|
||||||
void EmojiListWidget::showPicker() {
|
void EmojiListWidget::showPicker() {
|
||||||
if (_pickerSel < 0) return;
|
if (_pickerSel < 0) return;
|
||||||
|
|
||||||
|
@ -694,7 +914,7 @@ void EmojiListWidget::pickerHidden() {
|
||||||
updateSelected();
|
updateSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect EmojiListWidget::emojiRect(int section, int sel) {
|
QRect EmojiListWidget::emojiRect(int section, int sel) const {
|
||||||
Expects(_columnCount > 0);
|
Expects(_columnCount > 0);
|
||||||
|
|
||||||
auto info = sectionInfo(section);
|
auto info = sectionInfo(section);
|
||||||
|
@ -796,9 +1016,82 @@ void EmojiListWidget::refreshRecent() {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
_emoji[0] = Core::App().settings().recentEmojiSection();
|
_emoji[0] = Core::App().settings().recentEmojiSection();
|
||||||
_counts[0] = _emoji[0].size();
|
_counts[0] = _emoji[0].size();
|
||||||
|
refreshCustom();
|
||||||
resizeToWidth(width());
|
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) {
|
bool EmojiListWidget::eventHook(QEvent *e) {
|
||||||
if (e->type() == QEvent::ParentChange) {
|
if (e->type() == QEvent::ParentChange) {
|
||||||
if (_picker->parentWidget() != parentWidget()) {
|
if (_picker->parentWidget() != parentWidget()) {
|
||||||
|
@ -819,7 +1112,7 @@ void EmojiListWidget::updateSelected() {
|
||||||
auto sx = (rtl() ? width() - p.x() : p.x()) - _rowsLeft;
|
auto sx = (rtl() ? width() - p.x() : p.x()) - _rowsLeft;
|
||||||
if (sx >= 0 && sx < _columnCount * _singleSize.width()) {
|
if (sx >= 0 && sx < _columnCount * _singleSize.width()) {
|
||||||
newSelected = qFloor((p.y() - info.rowsTop) / _singleSize.height()) * _columnCount + qFloor(sx / _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;
|
newSelected = -1;
|
||||||
} else {
|
} else {
|
||||||
newSelected += Layout::PositionToIndex(info.section, 0);
|
newSelected += Layout::PositionToIndex(info.section, 0);
|
||||||
|
|
|
@ -16,11 +16,14 @@ template <typename ...Tags>
|
||||||
struct phrase;
|
struct phrase;
|
||||||
} // namespace tr
|
} // namespace tr
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui::Emoji {
|
||||||
namespace Emoji {
|
|
||||||
enum class Section;
|
enum class Section;
|
||||||
} // namespace Emoji
|
} // namespace Ui::Emoji
|
||||||
} // namespace Ui
|
|
||||||
|
namespace Ui::CustomEmoji {
|
||||||
|
class Instance;
|
||||||
|
struct RepaintRequest;
|
||||||
|
} // namespace Ui::CustomEmoji
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
|
@ -39,6 +42,7 @@ public:
|
||||||
EmojiListWidget(
|
EmojiListWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller);
|
not_null<Window::SessionController*> controller);
|
||||||
|
~EmojiListWidget();
|
||||||
|
|
||||||
using Section = Ui::Emoji::Section;
|
using Section = Ui::Emoji::Section;
|
||||||
|
|
||||||
|
@ -47,14 +51,16 @@ public:
|
||||||
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
|
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
|
||||||
|
|
||||||
void showEmojiSection(Section section);
|
void showEmojiSection(Section section);
|
||||||
Section currentSection(int yOffset) const;
|
[[nodiscard]] Section currentSection(int yOffset) const;
|
||||||
|
|
||||||
// Ui::AbstractTooltipShower interface.
|
// Ui::AbstractTooltipShower interface.
|
||||||
QString tooltipText() const override;
|
QString tooltipText() const override;
|
||||||
QPoint tooltipPos() const override;
|
QPoint tooltipPos() const override;
|
||||||
bool tooltipWindowActive() 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:
|
protected:
|
||||||
void visibleTopBottomUpdated(
|
void visibleTopBottomUpdated(
|
||||||
|
@ -85,29 +91,69 @@ private:
|
||||||
int rowsTop = 0;
|
int rowsTop = 0;
|
||||||
int rowsBottom = 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>
|
template <typename Callback>
|
||||||
bool enumerateSections(Callback callback) const;
|
bool enumerateSections(Callback callback) const;
|
||||||
SectionInfo sectionInfo(int section) const;
|
[[nodiscard]] SectionInfo sectionInfo(int section) const;
|
||||||
SectionInfo sectionInfoByOffset(int yOffset) const;
|
[[nodiscard]] SectionInfo sectionInfoByOffset(int yOffset) const;
|
||||||
|
[[nodiscard]] int sectionsCount() const;
|
||||||
|
|
||||||
void showPicker();
|
void showPicker();
|
||||||
void pickerHidden();
|
void pickerHidden();
|
||||||
void colorChosen(EmojiPtr emoji);
|
void colorChosen(EmojiPtr emoji);
|
||||||
bool checkPickerHide();
|
bool checkPickerHide();
|
||||||
|
void refreshCustom();
|
||||||
|
void unloadNotSeenCustom(int visibleTop, int visibleBottom);
|
||||||
|
|
||||||
void ensureLoaded(int section);
|
void ensureLoaded(int section);
|
||||||
void updateSelected();
|
void updateSelected();
|
||||||
void setSelected(int newSelected);
|
void setSelected(int newSelected);
|
||||||
|
|
||||||
void selectEmoji(EmojiPtr emoji);
|
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;
|
Footer *_footer = nullptr;
|
||||||
|
|
||||||
int _counts[kEmojiSectionCount];
|
int _counts[kEmojiSectionCount];
|
||||||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||||
|
std::vector<CustomSet> _custom;
|
||||||
|
base::flat_map<uint64, std::unique_ptr<CustomInstance>> _instances;
|
||||||
|
|
||||||
int _rowsLeft = 0;
|
int _rowsLeft = 0;
|
||||||
int _columnCount = 1;
|
int _columnCount = 1;
|
||||||
|
@ -122,7 +168,13 @@ private:
|
||||||
object_ptr<EmojiColorPicker> _picker;
|
object_ptr<EmojiColorPicker> _picker;
|
||||||
base::Timer _showPickerTimer;
|
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<EmojiPtr> _chosen;
|
||||||
|
rpl::event_stream<TabbedSelector::FileChosen> _customChosen;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -77,14 +78,18 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
|
||||||
i = all.erase(i);
|
i = all.erase(i);
|
||||||
continue;
|
continue;
|
||||||
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
|
} 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);
|
i = all.erase(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
|
if (!_session->premium()) {
|
||||||
if (Data::ParseCustomEmojiData(data).selfId != id) {
|
const auto document = _session->data().document(emoji.id);
|
||||||
i = all.erase(i);
|
if (document->isPremiumSticker()) {
|
||||||
continue;
|
i = all.erase(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
|
|
@ -462,7 +462,11 @@ rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
|
||||||
return emoji()->chosen();
|
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>(
|
auto never = rpl::never<TabbedSelector::FileChosen>(
|
||||||
) | rpl::type_erased();
|
) | rpl::type_erased();
|
||||||
return rpl::merge(
|
return rpl::merge(
|
||||||
|
|
|
@ -83,6 +83,7 @@ public:
|
||||||
Main::Session &session() const;
|
Main::Session &session() const;
|
||||||
|
|
||||||
rpl::producer<EmojiPtr> emojiChosen() const;
|
rpl::producer<EmojiPtr> emojiChosen() const;
|
||||||
|
rpl::producer<FileChosen> customEmojiChosen() const;
|
||||||
rpl::producer<FileChosen> fileChosen() const;
|
rpl::producer<FileChosen> fileChosen() const;
|
||||||
rpl::producer<PhotoChosen> photoChosen() const;
|
rpl::producer<PhotoChosen> photoChosen() const;
|
||||||
rpl::producer<InlineChosen> inlineResultChosen() const;
|
rpl::producer<InlineChosen> inlineResultChosen() const;
|
||||||
|
|
|
@ -708,7 +708,7 @@ void DocumentData::automaticLoadSettingsChanged() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_loader = nullptr;
|
_loader = nullptr;
|
||||||
_flags &= ~Flag::DownloadCancelled;
|
resetCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentData::finishLoad() {
|
void DocumentData::finishLoad() {
|
||||||
|
@ -867,7 +867,7 @@ void DocumentData::save(
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_flags &= ~Flag::DownloadCancelled;
|
resetCancelled();
|
||||||
|
|
||||||
if (_loader) {
|
if (_loader) {
|
||||||
if (fromCloud == LoadFromCloudOrLocal) {
|
if (fromCloud == LoadFromCloudOrLocal) {
|
||||||
|
@ -992,6 +992,10 @@ bool DocumentData::cancelled() const {
|
||||||
return (_flags & Flag::DownloadCancelled);
|
return (_flags & Flag::DownloadCancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DocumentData::resetCancelled() {
|
||||||
|
_flags &= ~Flag::DownloadCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
|
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
|
||||||
auto bitsCount = static_cast<int>(encoded5bit.size() * 8);
|
auto bitsCount = static_cast<int>(encoded5bit.size() * 8);
|
||||||
auto valuesCount = bitsCount / 5;
|
auto valuesCount = bitsCount / 5;
|
||||||
|
|
|
@ -115,6 +115,7 @@ public:
|
||||||
bool autoLoading = false);
|
bool autoLoading = false);
|
||||||
void cancel();
|
void cancel();
|
||||||
[[nodiscard]] bool cancelled() const;
|
[[nodiscard]] bool cancelled() const;
|
||||||
|
void resetCancelled();
|
||||||
[[nodiscard]] float64 progress() const;
|
[[nodiscard]] float64 progress() const;
|
||||||
[[nodiscard]] int64 loadOffset() const;
|
[[nodiscard]] int64 loadOffset() const;
|
||||||
[[nodiscard]] bool uploading() const;
|
[[nodiscard]] bool uploading() const;
|
||||||
|
|
|
@ -163,6 +163,7 @@ void CustomEmojiLoader::load(Fn<void(LoadResult)> loaded) {
|
||||||
.media = load->document->createMediaView(),
|
.media = load->document->createMediaView(),
|
||||||
.loaded = std::move(loaded),
|
.loaded = std::move(loaded),
|
||||||
});
|
});
|
||||||
|
load->process->media->owner()->resetCancelled();
|
||||||
load->process->media->checkStickerLarge();
|
load->process->media->checkStickerLarge();
|
||||||
if (load->process->media->loaded()) {
|
if (load->process->media->loaded()) {
|
||||||
check();
|
check();
|
||||||
|
@ -399,6 +400,12 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
std::move(update));
|
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() {
|
void CustomEmojiManager::request() {
|
||||||
auto ids = QVector<MTPlong>();
|
auto ids = QVector<MTPlong>();
|
||||||
ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size())));
|
ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size())));
|
||||||
|
|
|
@ -42,6 +42,10 @@ public:
|
||||||
QStringView data,
|
QStringView data,
|
||||||
Fn<void()> update);
|
Fn<void()> update);
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
SizeTag tag);
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
[[nodiscard]] Session &owner() const;
|
[[nodiscard]] Session &owner() const;
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
|
@ -1051,6 +1052,13 @@ void HistoryWidget::initTabbedSelector() {
|
||||||
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
|
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
|
||||||
}, lifetime());
|
}, 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(
|
selector->fileChosen(
|
||||||
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
|
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||||
controller()->sendingAnimation().appendSending(
|
controller()->sendingAnimation().appendSending(
|
||||||
|
|
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
@ -1828,6 +1829,12 @@ void ComposeControls::initTabbedSelector() {
|
||||||
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
|
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
|
||||||
}, wrap->lifetime());
|
}, wrap->lifetime());
|
||||||
|
|
||||||
|
using FileChosen = ChatHelpers::TabbedSelector::FileChosen;
|
||||||
|
selector->customEmojiChosen(
|
||||||
|
) | rpl::start_with_next([=](FileChosen data) {
|
||||||
|
Data::InsertCustomEmoji(_field, data.document);
|
||||||
|
}, wrap->lifetime());
|
||||||
|
|
||||||
selector->fileChosen(
|
selector->fileChosen(
|
||||||
) | rpl::start_to_stream(_fileChosen, wrap->lifetime());
|
) | rpl::start_to_stream(_fileChosen, wrap->lifetime());
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ constexpr auto kMaxSize = 128;
|
||||||
constexpr auto kMaxFrames = 512;
|
constexpr auto kMaxFrames = 512;
|
||||||
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
|
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
|
||||||
constexpr auto kCacheVersion = 1;
|
constexpr auto kCacheVersion = 1;
|
||||||
|
constexpr auto kPreloadFrames = 3;
|
||||||
|
|
||||||
struct CacheHeader {
|
struct CacheHeader {
|
||||||
int version = 0;
|
int version = 0;
|
||||||
|
@ -377,6 +378,8 @@ Renderer::Renderer(RendererDescriptor &&descriptor)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Renderer::~Renderer() = default;
|
||||||
|
|
||||||
void Renderer::frameReady(
|
void Renderer::frameReady(
|
||||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||||
crl::time duration,
|
crl::time duration,
|
||||||
|
@ -390,24 +393,35 @@ void Renderer::frameReady(
|
||||||
_cache.reserve(count);
|
_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);
|
_cache.add(duration, frame);
|
||||||
if (explicitRepaint && _repaint) {
|
if (explicitRepaint && _repaint) {
|
||||||
_repaint();
|
_repaint();
|
||||||
}
|
}
|
||||||
if (!duration) {
|
if (!duration) {
|
||||||
finish();
|
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 size = _cache.size();
|
||||||
const auto guard = base::make_weak(this);
|
const auto guard = base::make_weak(this);
|
||||||
crl::async([
|
crl::async([
|
||||||
=,
|
=,
|
||||||
frame = std::move(frame),
|
storage = std::move(storage),
|
||||||
generator = std::move(generator)
|
generator = std::move(generator)
|
||||||
]() mutable {
|
]() mutable {
|
||||||
auto rendered = generator->renderNext(
|
auto rendered = generator->renderNext(
|
||||||
std::move(frame),
|
std::move(storage),
|
||||||
QSize(size, size) * style::DevicePixelRatio(),
|
QSize(size, size) * style::DevicePixelRatio(),
|
||||||
Qt::KeepAspectRatio);
|
Qt::KeepAspectRatio);
|
||||||
crl::on_main(guard, [
|
crl::on_main(guard, [
|
||||||
|
@ -432,7 +446,13 @@ void Renderer::finish() {
|
||||||
}
|
}
|
||||||
|
|
||||||
PaintFrameResult Renderer::paint(QPainter &p, int x, int y, crl::time now) {
|
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) {
|
std::optional<Cached> Renderer::ready(const QString &entityData) {
|
||||||
|
|
|
@ -136,7 +136,7 @@ struct RendererDescriptor {
|
||||||
class Renderer final : public base::has_weak_ptr {
|
class Renderer final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
explicit Renderer(RendererDescriptor &&descriptor);
|
explicit Renderer(RendererDescriptor &&descriptor);
|
||||||
virtual ~Renderer() = default;
|
virtual ~Renderer();
|
||||||
|
|
||||||
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now);
|
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now);
|
||||||
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
|
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
|
||||||
|
@ -152,10 +152,14 @@ private:
|
||||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||||
crl::time duration,
|
crl::time duration,
|
||||||
QImage frame);
|
QImage frame);
|
||||||
|
void renderNext(
|
||||||
|
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||||
|
QImage storage);
|
||||||
void finish();
|
void finish();
|
||||||
|
|
||||||
Cache _cache;
|
Cache _cache;
|
||||||
std::unique_ptr<Ui::FrameGenerator> _generator;
|
std::unique_ptr<Ui::FrameGenerator> _generator;
|
||||||
|
QImage _storage;
|
||||||
Fn<void(QByteArray)> _put;
|
Fn<void(QByteArray)> _put;
|
||||||
Fn<void()> _repaint;
|
Fn<void()> _repaint;
|
||||||
Fn<std::unique_ptr<Loader>()> _loader;
|
Fn<std::unique_ptr<Loader>()> _loader;
|
||||||
|
@ -208,6 +212,8 @@ public:
|
||||||
Instance(
|
Instance(
|
||||||
Loading loading,
|
Loading loading,
|
||||||
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater);
|
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater);
|
||||||
|
Instance(const Instance&) = delete;
|
||||||
|
Instance &operator=(const Instance&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] QString entityData() const;
|
[[nodiscard]] QString entityData() const;
|
||||||
void paint(
|
void paint(
|
||||||
|
|
Loading…
Add table
Reference in a new issue