Improve emoji set box design.

This commit is contained in:
John Preston 2022-07-22 14:39:28 +03:00
parent 152bcb3570
commit b31a3ba5a3
20 changed files with 407 additions and 219 deletions

View file

@ -45,19 +45,24 @@ void AttachedStickers::request(
return;
}
// Single attached sticker pack.
const auto setData = result.v.front().match([&](const auto &data) {
return data.vset().match([&](const MTPDstickerSet &data) {
return &data;
});
const auto data = result.v.front().match([&](const auto &data) {
return &data.vset().data();
});
const auto setId = (setData->vid().v && setData->vaccess_hash().v)
const auto setId = (data->vid().v && data->vaccess_hash().v)
? StickerSetIdentifier{
.id = setData->vid().v,
.accessHash = setData->vaccess_hash().v }
: StickerSetIdentifier{ .shortName = qs(setData->vshort_name()) };
.id = data->vid().v,
.accessHash = data->vaccess_hash().v }
: StickerSetIdentifier{ .shortName = qs(data->vshort_name()) };
strongController->show(
Box<StickerSetBox>(strongController, setId),
Box<StickerSetBox>(
strongController,
setId,
(data->is_emojis()
? Data::StickersType::Emoji
: data->is_masks()
? Data::StickersType::Masks
: Data::StickersType::Stickers)),
Ui::LayerOption::KeepOther);
}).fail([=] {
_requestId = 0;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "menu/menu_send.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/image/image_location_factory.h"
#include "ui/text/text_utilities.h"
#include "ui/text/custom_emoji_instance.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/emoji_config.h"
#include "ui/toast/toast.h"
@ -115,7 +117,8 @@ public:
Inner(
QWidget *parent,
not_null<Window::SessionController*> controller,
const StickerSetIdentifier &set);
const StickerSetIdentifier &set,
Data::StickersType type);
[[nodiscard]] bool loaded() const;
[[nodiscard]] bool notInstalled() const;
@ -153,12 +156,15 @@ protected:
void leaveEventHook(QEvent *e) override;
private:
using CustomInstance = Ui::CustomEmoji::SeparateInstance;
struct Element {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *lottie = nullptr;
Media::Clip::ReaderPointer webm;
CustomInstance *emoji = nullptr;
Ui::Animations::Simple overAnimation;
mutable QImage premiumLock;
};
@ -178,6 +184,10 @@ private:
Media::Clip::Notification notification,
not_null<DocumentData*> document,
int index);
void setupEmoji(int index);
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
not_null<DocumentData*> document);
void customEmojiRepaint();
void updateSelected();
void setSelected(int selected);
@ -196,10 +206,17 @@ private:
void updateItems();
void repaintItems(crl::time now = 0);
not_null<Window::SessionController*> _controller;
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
std::vector<Element> _elements;
std::unique_ptr<Lottie::MultiPlayer> _lottiePlayer;
base::flat_map<
not_null<DocumentData*>,
std::unique_ptr<CustomInstance>> _instances;
std::unique_ptr<Ui::CustomEmoji::SimpleManager> _manager;
bool _repaintScheduled = false;
StickersPack _pack;
StickersByEmojiMap _emoji;
bool _loaded = false;
@ -226,6 +243,7 @@ private:
base::Timer _updateItemsTimer;
StickerSetIdentifier _input;
QMargins _padding;
mtpRequestId _installRequest = 0;
@ -246,9 +264,18 @@ private:
StickerSetBox::StickerSetBox(
QWidget*,
not_null<Window::SessionController*> controller,
const StickerSetIdentifier &set)
const StickerSetIdentifier &set,
Data::StickersType type)
: _controller(controller)
, _set(set) {
, _set(set)
, _type(type) {
}
StickerSetBox::StickerSetBox(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Data::StickersSet*> set)
: StickerSetBox(parent, controller, set->identifier(), set->type()) {
}
QPointer<Ui::BoxContent> StickerSetBox::Show(
@ -257,7 +284,10 @@ QPointer<Ui::BoxContent> StickerSetBox::Show(
if (const auto sticker = document->sticker()) {
if (sticker->set) {
return controller->show(
Box<StickerSetBox>(controller, sticker->set),
Box<StickerSetBox>(
controller,
sticker->set,
sticker->setType),
Ui::LayerOption::KeepOther).data();
}
}
@ -268,14 +298,18 @@ void StickerSetBox::prepare() {
setTitle(tr::lng_contacts_loading());
_inner = setInnerWidget(
object_ptr<Inner>(this, _controller, _set),
object_ptr<Inner>(this, _controller, _set, _type),
st::stickersScroll);
_controller->session().data().stickers().updated(
) | rpl::start_with_next([=] {
updateButtons();
}, lifetime());
setDimensions(st::boxWideWidth, st::stickersMaxHeight);
setDimensions(
st::boxWideWidth,
(_type == Data::StickersType::Emoji
? st::emojiSetMaxHeight
: st::stickersMaxHeight));
updateTitleAndButtons();
@ -476,10 +510,12 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) {
StickerSetBox::Inner::Inner(
QWidget *parent,
not_null<Window::SessionController*> controller,
const StickerSetIdentifier &set)
const StickerSetIdentifier &set,
Data::StickersType type)
: RpWidget(parent)
, _controller(controller)
, _api(&_controller->session().mtp())
, _manager(std::make_unique<Ui::CustomEmoji::SimpleManager>())
, _setId(set.id)
, _setAccessHash(set.accessHash)
, _setShortName(set.shortName)
@ -489,6 +525,9 @@ StickerSetBox::Inner::Inner(
[=] { repaintItems(); }))
, _updateItemsTimer([=] { updateItems(); })
, _input(set)
, _padding((type == Data::StickersType::Emoji)
? st::emojiSetPadding
: st::stickersPadding)
, _previewTimer([=] { showPreview(); }) {
setAttribute(Qt::WA_OpaquePaintEvent);
@ -613,16 +652,11 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
}
_perRow = isEmojiSet() ? kEmojiPerRow : kStickersPerRow;
_rowsCount = (_pack.size() + _perRow - 1) / _perRow;
const auto fullWidth = st::boxWideWidth;
const auto rowsLeft = st::stickersPadding.left();
const auto rowsRight = rowsLeft;
const auto singleWidth = (fullWidth - rowsLeft - rowsRight)
/ _perRow;
_singleSize = isEmojiSet()
? QSize(singleWidth, singleWidth)
: st::stickersSize;
_singleSize = isEmojiSet() ? st::emojiSetSize : st::stickersSize;
resize(st::stickersPadding.left() + _perRow * _singleSize.width(), st::stickersPadding.top() + _rowsCount * _singleSize.height() + st::stickersPadding.bottom());
resize(
_padding.left() + _perRow * _singleSize.width(),
_padding.top() + _rowsCount * _singleSize.height() + _padding.bottom());
_loaded = true;
updateSelected();
@ -862,8 +896,8 @@ void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 t
_elements[index].overAnimation.start([=] {
const auto row = index / _perRow;
const auto column = index % _perRow;
const auto left = st::stickersPadding.left() + column * _singleSize.width();
const auto top = st::stickersPadding.top() + row * _singleSize.height();
const auto left = _padding.left() + column * _singleSize.width();
const auto top = _padding.top() + row * _singleSize.height();
rtlupdate(left, top, _singleSize.width(), _singleSize.height());
}, from, to, st::emojiPanDuration);
}
@ -903,8 +937,8 @@ not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
QPoint l(mapFromGlobal(p));
if (rtl()) l.setX(width() - l.x());
int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / _singleSize.height()) : -1;
int32 col = (l.x() >= st::stickersPadding.left()) ? qFloor((l.x() - st::stickersPadding.left()) / _singleSize.width()) : -1;
int32 row = (l.y() >= _padding.top()) ? qFloor((l.y() - _padding.top()) / _singleSize.height()) : -1;
int32 col = (l.x() >= _padding.left()) ? qFloor((l.x() - _padding.left()) / _singleSize.width()) : -1;
if (row >= 0 && col >= 0 && col < _perRow) {
int32 result = row * _perRow + col;
return (result < _pack.size()) ? result : -1;
@ -915,6 +949,8 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
_repaintScheduled = false;
p.fillRect(e->rect(), st::boxBg);
if (_elements.empty()) {
return;
@ -933,7 +969,9 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
if (index >= _elements.size()) {
break;
}
const auto pos = QPoint(st::stickersPadding.left() + j * _singleSize.width(), st::stickersPadding.top() + i * _singleSize.height());
const auto pos = QPoint(
_padding.left() + j * _singleSize.width(),
_padding.top() + i * _singleSize.height());
paintSticker(p, index, pos, paused, now);
}
}
@ -984,7 +1022,7 @@ void StickerSetBox::Inner::visibleTopBottomUpdated(
}
}
};
const auto rowsTop = st::stickersPadding.top();
const auto rowsTop = _padding.top();
const auto singleHeight = _singleSize.height();
const auto rowsBottom = rowsTop + _rowsCount * singleHeight;
if (visibleTop >= rowsTop + singleHeight && visibleTop < rowsBottom) {
@ -1058,6 +1096,37 @@ void StickerSetBox::Inner::clipCallback(
updateItems();
}
void StickerSetBox::Inner::setupEmoji(int index) {
auto &element = _elements[index];
element.emoji = resolveCustomInstance(element.document);
}
auto StickerSetBox::Inner::resolveCustomInstance(
not_null<DocumentData*> document)
-> not_null<CustomInstance*> {
const auto i = _instances.find(document);
if (i != end(_instances)) {
return i->second.get();
}
auto instance = _manager->make(
_controller->session().data().customEmojiManager().createLoader(
document,
Data::CustomEmojiManager::SizeTag::Large),
[=] { customEmojiRepaint(); });
return _instances.emplace(
document,
std::move(instance)
).first->second.get();
}
void StickerSetBox::Inner::customEmojiRepaint() {
if (_repaintScheduled) {
return;
}
_repaintScheduled = true;
update();
}
void StickerSetBox::Inner::paintSticker(
Painter &p,
int index,
@ -1080,7 +1149,9 @@ void StickerSetBox::Inner::paintSticker(
&& !_controller->session().premium();
media->checkStickerSmall();
if (media->loaded()) {
if (sticker->setType == Data::StickersType::Emoji) {
const_cast<Inner*>(this)->setupEmoji(index);
} else if (media->loaded()) {
if (sticker->isLottie() && !element.lottie) {
const_cast<Inner*>(this)->setupLottie(index);
} else if (sticker->isWebm() && !element.webm) {
@ -1095,7 +1166,15 @@ void StickerSetBox::Inner::paintSticker(
(_singleSize.width() - size.width()) / 2,
(_singleSize.height() - size.height()) / 2);
auto lottieFrame = QImage();
if (element.lottie && element.lottie->ready()) {
if (element.emoji) {
element.emoji->object.paint(
p,
ppos.x(),
ppos.y(),
now,
st::windowBgOver->c,
paused);
} else if (element.lottie && element.lottie->ready()) {
lottieFrame = element.lottie->frame();
p.drawImage(
QRect(ppos, lottieFrame.size() / cIntRetinaFactor()),

View file

@ -19,12 +19,21 @@ namespace Ui {
class PlainShadow;
} // namespace Ui
namespace Data {
class StickersSet;
} // namespace Data
class StickerSetBox final : public Ui::BoxContent {
public:
StickerSetBox(
QWidget*,
not_null<Window::SessionController*> controller,
const StickerSetIdentifier &set);
const StickerSetIdentifier &set,
Data::StickersType type);
StickerSetBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<Data::StickersSet*> set);
static QPointer<Ui::BoxContent> Show(
not_null<Window::SessionController*> controller,
@ -48,6 +57,7 @@ private:
const not_null<Window::SessionController*> _controller;
const StickerSetIdentifier _set;
const Data::StickersType _type;
class Inner;
QPointer<Inner> _inner;

View file

@ -1806,7 +1806,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
const auto showSetByRow = [&](const Row &row) {
setSelected(SelectedRow());
_controller->show(
Box<StickerSetBox>(_controller, row.set->identifier()),
Box<StickerSetBox>(_controller, row.set),
Ui::LayerOption::KeepOther);
};
if (selectedIndex >= 0 && !_inDragArea) {

View file

@ -79,6 +79,9 @@ stickersFeaturedInstalled: icon {{ "chat/input_save", lightButtonFg }};
stickersMaxHeight: 320px;
stickersPadding: margins(19px, 13px, 19px, 13px);
stickersSize: size(64px, 64px);
emojiSetPadding: margins(12px, 0px, 12px, 0px);
emojiSetMaxHeight: 197px;
emojiSetSize: size(42px, 39px);
stickersScroll: ScrollArea(boxScroll) {
deltat: 19px;
deltab: 9px;
@ -225,6 +228,9 @@ stickerIconMove: 400;
stickerPreviewDuration: 150;
stickerPreviewMin: 0.1;
emojiIconWidth: 35px;
emojiIconArea: 32px;
stickerGroupCategorySize: 28px;
stickerGroupCategoryAbout: defaultTextStyle;
stickerGroupCategoryAddMargin: margins(0px, 10px, 0px, 5px);

View file

@ -96,17 +96,9 @@ private:
};
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,
bool recentOnly = false);
struct EmojiListWidget::CustomInstance : Ui::CustomEmoji::SeparateInstance {
using SeparateInstance::SeparateInstance;
Ui::CustomEmoji::Instance emoji;
Ui::CustomEmoji::Object object;
bool recentOnly = false;
};
@ -115,20 +107,6 @@ struct EmojiListWidget::RecentOne {
RecentEmojiId id;
};
EmojiListWidget::CustomInstance::CustomInstance(
std::unique_ptr<Ui::CustomEmoji::Loader> loader,
Fn<void(
not_null<Ui::CustomEmoji::Instance*>,
Ui::CustomEmoji::RepaintRequest)> repaintLater,
Fn<void()> repaint,
bool recentOnly)
: emoji(
Ui::CustomEmoji::Loading(std::move(loader), Ui::CustomEmoji::Preview()),
std::move(repaintLater))
, object(&emoji, std::move(repaint))
, recentOnly(recentOnly) {
}
EmojiColorPicker::EmojiColorPicker(QWidget *parent)
: RpWidget(parent) {
setMouseTracking(true);
@ -540,23 +518,42 @@ 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();
if (info.rowsBottom <= visibleTop || info.rowsTop >= visibleBottom) {
unloadCustomIn(info);
}
return true;
});
}
void EmojiListWidget::unloadAllCustom() {
enumerateSections([&](const SectionInfo &info) {
unloadCustomIn(info);
return true;
});
}
void EmojiListWidget::unloadCustomIn(const SectionInfo &info) {
if (!info.section && _recentPainted) {
_recentPainted = false;
for (const auto &single : _recent) {
if (const auto instance = single.instance) {
instance->object.unload();
}
}
return;
} else if (info.section < kEmojiSectionCount) {
return;
}
auto &custom = _custom[info.section - kEmojiSectionCount];
if (!custom.painted) {
return;
}
custom.painted = false;
for (const auto &single : custom.list) {
single.instance->object.unload();
}
}
object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
Expects(_footer == nullptr);
@ -1020,7 +1017,7 @@ void EmojiListWidget::displaySet(uint64 setId) {
auto it = sets.find(setId);
if (it != sets.cend()) {
checkHideWithBox(controller()->show(
Box<StickerSetBox>(controller(), it->second->identifier()),
Box<StickerSetBox>(controller(), it->second.get()),
Ui::LayerOption::KeepOther).data());
}
}
@ -1206,9 +1203,14 @@ void EmojiListWidget::processHideFinished() {
_picker->hideFast();
_pickerSelected = v::null;
}
unloadAllCustom();
clearSelection();
}
void EmojiListWidget::processPanelHideFinished() {
unloadAllCustom();
}
void EmojiListWidget::refreshRecent() {
clearSelection();
fillRecent();
@ -1302,11 +1304,12 @@ auto EmojiListWidget::customInstanceWithLoader(
});
}
};
return std::make_unique<CustomInstance>(
auto result = std::make_unique<CustomInstance>(
std::move(loader),
std::move(repaintDelayed),
std::move(repaintNow),
recentOnly);
std::move(repaintNow));
result->recentOnly = recentOnly;
return result;
}
auto EmojiListWidget::resolveCustomInstance(

View file

@ -96,6 +96,7 @@ protected:
TabbedSelector::InnerFooter *getFooter() const override;
void processHideFinished() override;
void processPanelHideFinished() override;
int countDesiredHeight(int newWidth) override;
private:
@ -179,6 +180,8 @@ private:
bool checkPickerHide();
void refreshCustom();
void unloadNotSeenCustom(int visibleTop, int visibleBottom);
void unloadAllCustom();
void unloadCustomIn(const SectionInfo &info);
void ensureLoaded(int section);
void updateSelected();

View file

@ -41,36 +41,12 @@ constexpr auto kAnimationDuration = crl::time(120);
} // namespace
struct SuggestionsWidget::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;
};
SuggestionsWidget::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)) {
}
SuggestionsWidget::SuggestionsWidget(
QWidget *parent,
not_null<Main::Session*> session)
: RpWidget(parent)
, _session(session)
, _repaintTimer([=] { invokeRepaints(); })
, _manager(std::make_unique<Ui::CustomEmoji::SimpleManager>())
, _oneWidth(st::emojiSuggestionSize)
, _padding(st::emojiSuggestionsPadding) {
resize(
@ -180,77 +156,23 @@ auto SuggestionsWidget::resolveCustomInstance(
if (i != end(_instances)) {
return i->second.get();
}
const auto repaintDelayed = [=](
not_null<Ui::CustomEmoji::Instance*> instance,
Ui::CustomEmoji::RepaintRequest request) {
if (_instances.empty() || !request.when) {
return;
}
auto &when = _repaints[request.duration];
if (when < request.when) {
when = request.when;
}
if (_repaintTimerScheduled) {
return;
}
scheduleRepaintTimer();
};
const auto repaintNow = [=] {
update();
};
auto instance = std::make_unique<CustomInstance>(
auto instance = _manager->make(
_session->data().customEmojiManager().createLoader(
document,
Data::CustomEmojiManager::SizeTag::Large),
std::move(repaintDelayed),
std::move(repaintNow));
[=] { customEmojiRepaint(); });
return _instances.emplace(
document,
std::move(instance)
).first->second.get();
}
void SuggestionsWidget::scheduleRepaintTimer() {
_repaintTimerScheduled = true;
Ui::PostponeCall(this, [=] {
_repaintTimerScheduled = false;
auto next = crl::time();
for (const auto &[duration, when] : _repaints) {
if (!next || next > when) {
next = 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 SuggestionsWidget::invokeRepaints() {
_repaintNext = 0;
auto invoke = false;
const auto now = crl::now();
for (auto i = begin(_repaints); i != end(_repaints);) {
if (i->second > now) {
++i;
continue;
}
invoke = true;
i = _repaints.erase(i);
void SuggestionsWidget::customEmojiRepaint() {
if (_repaintScheduled) {
return;
}
if (invoke) {
update();
}
scheduleRepaintTimer();
_repaintScheduled = true;
update();
}
SuggestionsWidget::Row::Row(
@ -378,6 +300,8 @@ void SuggestionsWidget::scrollByWheelEvent(not_null<QWheelEvent*> e) {
void SuggestionsWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
_repaintScheduled = false;
const auto clip = e->rect();
p.fillRect(clip, st::boxBg);

View file

@ -19,11 +19,16 @@ class Session;
} // namespace Main
namespace Ui {
class InnerDropdown;
class InputField;
} // namespace Ui
namespace Emoji {
namespace Ui::CustomEmoji {
struct SeparateInstance;
class SimpleManager;
} // namespace Ui::CustomEmoji
namespace Ui::Emoji {
class SuggestionsWidget final : public Ui::RpWidget {
public:
@ -43,7 +48,7 @@ public:
[[nodiscard]] rpl::producer<Chosen> triggered() const;
private:
struct CustomInstance;
using CustomInstance = Ui::CustomEmoji::SeparateInstance;
struct Row {
Row(not_null<EmojiPtr> emoji, const QString &replacement);
@ -89,19 +94,17 @@ private:
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
not_null<DocumentData*> document);
void scheduleRepaintTimer();
void invokeRepaints();
void customEmojiRepaint();
const not_null<Main::Session*> _session;
QString _query;
std::vector<Row> _rows;
base::flat_map<
not_null<DocumentData*>,
std::unique_ptr<CustomInstance>> _instances;
base::flat_map<crl::time, crl::time> _repaints;
bool _repaintTimerScheduled = false;
base::Timer _repaintTimer;
crl::time _repaintNext = 0;
std::unique_ptr<Ui::CustomEmoji::SimpleManager> _manager;
bool _repaintScheduled = false;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
@ -190,5 +193,4 @@ private:
};
} // namespace Emoji
} // namespace Ui
} // namespace Ui::Emoji

View file

@ -139,8 +139,8 @@ bool StickersListFooter::ScrollState::animationCallback(crl::time now) {
return false;
}
x.update(dt, anim::linear);
selectionX.update(dt, anim::linear);
selectionWidth.update(dt, anim::linear);
selectionX.update(dt, anim::easeOutCubic);
selectionWidth.update(dt, anim::easeOutCubic);
return true;
}
@ -151,7 +151,8 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
, _settingsButtonVisible(descriptor.settingsButtonVisible)
, _iconState([=] { update(); })
, _subiconState([=] { update(); })
, _selectionBg(st::roundRadiusSmall, st::windowBgRipple)
, _selectionBg(st::roundRadiusLarge, st::windowBgRipple)
, _subselectionBg(st::emojiIconArea / 2, st::windowBgRipple)
, _barSelection(descriptor.barSelection) {
setMouseTracking(true);
@ -299,14 +300,13 @@ void StickersListFooter::enumerateIcons(
const auto shift = _iconsLeft - iconsX;
const auto emojiId = AllEmojiSectionSetId();
const auto right = width();
const auto single = st::stickerIconWidth;
for (auto i = 0, count = int(_icons.size()); i != count; ++i) {
auto &icon = _icons[i];
const auto width = (icon.setId == emojiId)
? _subiconsWidthAnimation.value(_subiconsExpanded
? _subiconsWidth
: single)
: single;
: _singleWidth)
: _singleWidth;
const auto shifted = shift + left;
const auto visible = (shifted + width > 0 && shifted < right);
const auto result = callback({
@ -331,20 +331,19 @@ void StickersListFooter::enumerateSubicons(
const auto right = _subiconsWidth;
using Section = Ui::Emoji::Section;
for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) {
const auto width = st::stickerIconWidth;
const auto shifted = shift + left;
const auto visible = (shifted + width > 0 && shifted < right);
const auto visible = (shifted + _singleWidth > 0 && shifted < right);
const auto result = callback({
.index = i - int(Section::People),
.left = left,
.adjustedLeft = shifted,
.width = int(base::SafeRound(width)),
.width = _singleWidth,
.visible = visible,
});
if (!result) {
break;
}
left += width;
left += _singleWidth;
}
}
@ -432,8 +431,8 @@ void StickersListFooter::updateEmojiSectionWidth() {
_subiconsExpanded = expanded;
_subiconsWidthAnimation.start(
[=] { updateEmojiWidthCallback(); },
expanded ? st::stickerIconWidth : _subiconsWidth,
expanded ? _subiconsWidth : st::stickerIconWidth,
expanded ? _singleWidth : _subiconsWidth,
expanded ? _subiconsWidth : _singleWidth,
st::slideDuration);
}
@ -580,12 +579,27 @@ void StickersListFooter::paintSelectionBg(Painter &p) const {
selx = width() - selx - selw;
}
const auto skip = st::emojiIconSelectSkip;
const auto sely = _iconsTop
+ (st::emojiFooterHeight - st::stickerIconWidth) / 2;
const auto selh = st::stickerIconWidth;
const auto rect = QRect(selx, sely, selw, selh);
const auto fill = rect.marginsRemoved({ skip, skip, skip, skip });
_selectionBg.paint(p, fill);
const auto sely = _iconsTop;
const auto area = st::emojiIconArea;
const auto rect = QRect(
QPoint(selx, sely) + _areaPosition,
QSize(selw - 2 * _areaPosition.x(), area));
if (rect.width() == rect.height() || _subiconsWidth <= _singleWidth) {
_selectionBg.paint(p, rect);
} else if (selw == _subiconsWidth) {
_subselectionBg.paint(p, rect);
} else {
PainterHighQualityEnabler hq(p);
const auto progress = (selw - _singleWidth)
/ float64(_subiconsWidth - _singleWidth);
const auto radius = anim::interpolate(
st::roundRadiusLarge,
area / 2,
progress);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgRipple);
p.drawRoundedRect(rect, radius, radius);
}
}
void StickersListFooter::paintSelectionBar(Painter &p) const {
@ -864,17 +878,17 @@ void StickersListFooter::updateSelected() {
auto x = p.x(), y = p.y();
if (rtl()) x = width() - x;
const auto settingsLeft = width() - _iconsRight;
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
const auto searchLeft = _iconsLeft - _singleWidth;
auto newOver = OverState(SpecialOver::None);
if (_searchButtonVisible
&& x >= searchLeft
&& x < searchLeft + st::stickerIconWidth
&& x < searchLeft + _singleWidth
&& y >= _iconsTop
&& y < _iconsTop + st::emojiFooterHeight) {
newOver = SpecialOver::Search;
} else if (_settingsButtonVisible
&& x >= settingsLeft
&& x < settingsLeft + st::stickerIconWidth
&& x < settingsLeft + _singleWidth
&& y >= _iconsTop
&& y < _iconsTop + st::emojiFooterHeight) {
if (!_icons.empty() && !hasOnlyFeaturedSets()) {
@ -975,6 +989,17 @@ void StickersListFooter::refreshIconsGeometry(
_iconState.selectionWidth.finish();
_iconState.animationStart = 0;
_iconState.animation.stop();
if (_barSelection) {
_singleWidth = st::stickerIconWidth;
} else if (_icons.size() > 1
&& _icons[1].setId == EmojiSectionSetId(EmojiSection::People)) {
_singleWidth = (width() - _iconsLeft - _iconsRight) / _icons.size();
} else {
_singleWidth = st::emojiIconWidth;
}
_areaPosition = QPoint(
(_singleWidth - st::emojiIconArea) / 2,
(st::emojiFooterHeight - st::emojiIconArea) / 2);
refreshScrollableDimensions();
updateSelected();
validateSelectedIcon(_activeByScrollId, animations);
@ -988,18 +1013,18 @@ void StickersListFooter::refreshSubiconsGeometry() {
_subiconState.selectionWidth.finish();
_subiconState.animationStart = 0;
_subiconState.animation.stop();
const auto single = st::stickerIconWidth;
const auto half = single / 2;
const auto half = _singleWidth / 2;
const auto count = int(Section::Symbols) - int(Section::Recent);
const auto widthMax = count * single;
const auto widthMin = 4 * single + half;
const auto collapsedWidth = int(_icons.size()) * single;
const auto widthMax = count * _singleWidth;
const auto widthMin = 4 * _singleWidth + half;
const auto collapsedWidth = int(_icons.size()) * _singleWidth;
_subiconsWidth = std::clamp(
width() + single - collapsedWidth,
width() + _singleWidth - collapsedWidth,
widthMin,
widthMax);
if (_subiconsWidth < widthMax) {
_subiconsWidth = ((_subiconsWidth - half) / single) * single + half;
_subiconsWidth = half
+ (((_subiconsWidth - half) / _singleWidth) * _singleWidth);
}
_subiconState.max = std::max(
widthMax - _subiconsWidth,
@ -1020,16 +1045,16 @@ void StickersListFooter::paintStickerSettingsIcon(Painter &p) const {
st::stickersSettings.paint(
p,
settingsLeft
+ (st::stickerIconWidth - st::stickersSettings.width()) / 2,
+ (_singleWidth - st::stickersSettings.width()) / 2,
_iconsTop + st::emojiCategory.iconPosition.y(),
width());
}
void StickersListFooter::paintSearchIcon(Painter &p) const {
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
const auto searchLeft = _iconsLeft - _singleWidth;
st::stickersSearch.paint(
p,
searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2,
searchLeft + (_singleWidth - st::stickersSearch.width()) / 2,
_iconsTop + st::emojiCategory.iconPosition.y(),
width());
}
@ -1100,7 +1125,7 @@ void StickersListFooter::updateSetIcon(uint64 setId) {
}
void StickersListFooter::updateSetIconAt(int left) {
update(left, _iconsTop, st::stickerIconWidth, st::emojiFooterHeight);
update(left, _iconsTop, _singleWidth, st::emojiFooterHeight);
}
void StickersListFooter::paintSetIcon(
@ -1118,8 +1143,7 @@ void StickersListFooter::paintSetIcon(
: icon.stickerMedia
? icon.stickerMedia->thumbnail()
: nullptr;
const auto x = info.adjustedLeft
+ (st::stickerIconWidth - icon.pixw) / 2;
const auto x = info.adjustedLeft + (_singleWidth - icon.pixw) / 2;
const auto y = _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2;
if (icon.lottie && icon.lottie->ready()) {
const auto frame = icon.lottie->frame();
@ -1130,8 +1154,7 @@ void StickersListFooter::paintSetIcon(
}
p.drawImage(
QRect(
(info.adjustedLeft
+ (st::stickerIconWidth - size.width()) / 2),
(info.adjustedLeft + (_singleWidth - size.width()) / 2),
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
size.width(),
size.height()),
@ -1166,7 +1189,7 @@ void StickersListFooter::paintSetIcon(
icon.megagroup->paintUserpicLeft(
p,
icon.megagroupUserpic,
info.adjustedLeft + (st::stickerIconWidth - size) / 2,
info.adjustedLeft + (_singleWidth - size) / 2,
_iconsTop + (st::emojiFooterHeight - size) / 2,
width(),
st::stickerGroupCategorySize);
@ -1174,7 +1197,7 @@ void StickersListFooter::paintSetIcon(
validatePremiumIcon();
const auto size = st::stickersPremium.size();
p.drawImage(
info.adjustedLeft + (st::stickerIconWidth - size.width()) / 2,
info.adjustedLeft + (_singleWidth - size.width()) / 2,
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
_premiumIcon);
} else {
@ -1207,12 +1230,12 @@ void StickersListFooter::paintSetIcon(
const auto paintOne = [&](int left, const style::icon *icon) {
icon->paint(
p,
left + (st::stickerIconWidth - icon->width()) / 2,
left + (_singleWidth - icon->width()) / 2,
_iconsTop + (st::emojiFooterHeight - icon->height()) / 2,
width());
};
if (_icons[info.index].setId == AllEmojiSectionSetId()
&& info.width > st::stickerIconWidth) {
&& info.width > _singleWidth) {
const auto skip = st::emojiIconSelectSkip;
p.save();
p.setClipRect(

View file

@ -226,11 +226,13 @@ private:
int _iconsLeft = 0;
int _iconsRight = 0;
int _iconsTop = 0;
int _singleWidth = 0;
QPoint _areaPosition;
ScrollState _iconState;
ScrollState _subiconState;
Ui::RoundRect _selectionBg;
Ui::RoundRect _selectionBg, _subselectionBg;
Ui::Animations::Simple _subiconsWidthAnimation;
int _subiconsWidth = 0;
bool _subiconsExpanded = false;

View file

@ -2611,7 +2611,7 @@ void StickersListWidget::displaySet(uint64 setId) {
auto it = sets.find(setId);
if (it != sets.cend()) {
checkHideWithBox(controller()->show(
Box<StickerSetBox>(controller(), it->second->identifier()),
Box<StickerSetBox>(controller(), it->second.get()),
Ui::LayerOption::KeepOther).data());
}
}

View file

@ -82,7 +82,10 @@ bool ShowStickerSet(
Core::App().hideMediaView();
controller->show(Box<StickerSetBox>(
controller,
StickerSetIdentifier{ .shortName = match->captured(2) }));
StickerSetIdentifier{ .shortName = match->captured(2) },
(match->captured(1) == "addemoji"
? Data::StickersType::Emoji
: Data::StickersType::Stickers)));
controller->window().activate();
return true;
}

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_document.h"
#include "data/stickers/data_stickers.h"
#include "storage/file_download.h"
#include "ui/image/image.h"
@ -97,6 +98,14 @@ StickerSetIdentifier StickersSet::identifier() const {
};
}
StickersType StickersSet::type() const {
return (flags & StickersSetFlag::Emoji)
? StickersType::Emoji
: (flags & StickersSetFlag::Masks)
? StickersType::Masks
: StickersType::Stickers;
}
void StickersSet::setThumbnail(const ImageWithLocation &data) {
Data::UpdateCloudFile(
_thumbnail,

View file

@ -24,6 +24,8 @@ using SavedGifs = QVector<DocumentData*>;
using StickersPack = QVector<DocumentData*>;
using StickersByEmojiMap = QMap<EmojiPtr, StickersPack>;
enum class StickersType : uchar;
class StickersSet;
using StickersSets = base::flat_map<uint64, std::unique_ptr<StickersSet>>;
@ -81,6 +83,7 @@ public:
[[nodiscard]] MTPInputStickerSet mtpInput() const;
[[nodiscard]] StickerSetIdentifier identifier() const;
[[nodiscard]] StickersType type() const;
void setThumbnail(const ImageWithLocation &data);

View file

@ -1043,7 +1043,8 @@ void GenerateItems(
controller->show(
Box<StickerSetBox>(
controller,
Data::FromInputSet(set)),
Data::FromInputSet(set),
Data::StickersType::Stickers),
Ui::LayerOption::CloseOther);
}
});

View file

@ -1324,7 +1324,10 @@ void AddEmojiPacksAction(
}
// Single used emoji pack.
strong->show(
Box<StickerSetBox>(strong, packIds.front()),
Box<StickerSetBox>(
strong,
packIds.front(),
Data::StickersType::Emoji),
Ui::LayerOption::KeepOther);
});

View file

@ -195,7 +195,7 @@ void StickerToast::showWithTitle(const QString &title) {
}
button->setClickedCallback([=] {
_controller->show(
Box<StickerSetBox>(_controller, _for->sticker()->set),
Box<StickerSetBox>(_controller, _for->sticker()->set, setType),
Ui::LayerOption::KeepOther);
hideToast();
});

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/custom_emoji_instance.h"
#include "ui/effects/frame_generator.h"
#include "ui/ui_utility.h"
#include <crl/crl_async.h>
#include <lz4.h>
@ -647,4 +648,84 @@ void Object::repaint() {
_repaint();
}
SeparateInstance::SeparateInstance(
std::unique_ptr<Loader> loader,
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater,
Fn<void()> repaint)
: emoji(Loading(std::move(loader), Preview()), std::move(repaintLater))
, object(&emoji, std::move(repaint)) {
}
SimpleManager::SimpleManager()
: _repaintTimer([=] { invokeRepaints(); }) {
}
std::unique_ptr<SeparateInstance> SimpleManager::make(
std::unique_ptr<Loader> loader,
Fn<void()> repaint) {
auto repaintLater = [=](
not_null<Instance*> instance,
RepaintRequest request) {
if (!request.when) {
return;
}
auto &when = _repaints[request.duration];
if (when < request.when) {
when = request.when;
}
if (_repaintTimerScheduled) {
return;
}
scheduleRepaintTimer();
};
_simpleRepaint = repaint;
return std::make_unique<SeparateInstance>(
std::move(loader),
std::move(repaintLater),
std::move(repaint));
}
void SimpleManager::scheduleRepaintTimer() {
_repaintTimerScheduled = true;
Ui::PostponeCall(this, [=] {
_repaintTimerScheduled = false;
auto next = crl::time();
for (const auto &[duration, when] : _repaints) {
if (!next || next > when) {
next = 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 SimpleManager::invokeRepaints() {
_repaintNext = 0;
auto invoke = false;
const auto now = crl::now();
for (auto i = begin(_repaints); i != end(_repaints);) {
if (i->second > now) {
++i;
continue;
}
invoke = true;
i = _repaints.erase(i);
}
if (invoke && _simpleRepaint) {
_simpleRepaint();
}
scheduleRepaintTimer();
}
} // namespace Ui::CustomEmoji

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_block.h"
#include "base/weak_ptr.h"
#include "base/bytes.h"
#include "base/timer.h"
class QColor;
class QPainter;
@ -266,4 +267,34 @@ private:
};
struct SeparateInstance {
SeparateInstance(
std::unique_ptr<Loader> loader,
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater,
Fn<void()> repaint);
Instance emoji;
Object object;
};
class SimpleManager final : public base::has_weak_ptr {
public:
SimpleManager();
[[nodiscard]] std::unique_ptr<SeparateInstance> make(
std::unique_ptr<Loader> loader,
Fn<void()> repaint);
private:
void scheduleRepaintTimer();
void invokeRepaints();
base::flat_map<crl::time, crl::time> _repaints;
bool _repaintTimerScheduled = false;
crl::time _repaintNext = 0;
base::Timer _repaintTimer;
Fn<void()> _simpleRepaint;
};
} // namespace Ui::CustomEmoji