mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +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/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 §ion : _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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue