Seamless switch from strip icons to custom emoji.

This commit is contained in:
John Preston 2022-08-23 20:35:48 +03:00
parent 4762c7a4fd
commit 96805b62b2
8 changed files with 290 additions and 138 deletions

View File

@ -695,9 +695,7 @@ int EmojiListWidget::countDesiredHeight(int newWidth) {
}
int EmojiListWidget::defaultMinimalHeight() const {
return (_mode != Mode::Full)
? st::emojiPanArea.height()
: Inner::defaultMinimalHeight();
return Inner::defaultMinimalHeight();
}
void EmojiListWidget::ensureLoaded(int section) {
@ -961,10 +959,9 @@ void EmojiListWidget::drawRecent(
} else {
drawEmoji(p, context, position, *emoji);
}
} else {
Assert(_recent[index].custom != nullptr);
} else if (const auto custom = _recent[index].custom) {
position += _innerPosition + _customPosition;
_recent[index].custom->paint(p, {
const auto paintContext = Ui::Text::CustomEmoji::Context{
.preview = st::windowBgRipple->c,
.size = QSize(_customSingleSize, _customSingleSize),
.now = now,
@ -972,7 +969,10 @@ void EmojiListWidget::drawRecent(
.position = position,
.paused = paused,
.scaled = context.expanding,
});
};
custom->paint(p, paintContext);
} else {
Unexpected("Empty custom emoji in EmojiListWidget::drawRecent.");
}
}
@ -1597,14 +1597,11 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
}
auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId());
auto custom = _customRecentFactory
? _customRecentFactory(documentId, repaint)
: nullptr;
if (!custom) {
custom = session().data().customEmojiManager().create(
? _customRecentFactory(documentId, std::move(repaint))
: session().data().customEmojiManager().create(
documentId,
std::move(repaint),
Data::CustomEmojiManager::SizeTag::Large);
}
return _customEmoji.emplace(
documentId,
CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true }
@ -1857,7 +1854,6 @@ QPoint EmojiListWidget::buttonRippleTopLeft(int section) const {
void EmojiListWidget::refreshEmoji() {
refreshRecent();
refreshCustom();
resizeToWidth(width());
}
void EmojiListWidget::showSet(uint64 setId) {

View File

@ -41,10 +41,33 @@ public:
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
bool ready() override;
private:
std::unique_ptr<Ui::Text::CustomEmoji> _real;
QPoint _shift;
const std::unique_ptr<Ui::Text::CustomEmoji> _real;
const QPoint _shift;
};
class StripEmoji final : public Ui::Text::CustomEmoji {
public:
StripEmoji(
std::unique_ptr<Ui::Text::CustomEmoji> wrapped,
not_null<Strip*> strip,
QPoint shift,
int index);
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
bool ready() override;
private:
const std::unique_ptr<Ui::Text::CustomEmoji> _wrapped;
const not_null<Strip*> _strip;
const QPoint _shift;
const int _index = 0;
bool _switched = false;
};
@ -74,6 +97,45 @@ void ShiftedEmoji::unload() {
_real->unload();
}
bool ShiftedEmoji::ready() {
return _real->ready();
}
StripEmoji::StripEmoji(
std::unique_ptr<Ui::Text::CustomEmoji> wrapped,
not_null<Strip*> strip,
QPoint shift,
int index)
: _wrapped(std::move(wrapped))
, _strip(strip)
, _shift(shift)
, _index(index) {
}
QString StripEmoji::entityData() {
return _wrapped->entityData();
}
void StripEmoji::paint(QPainter &p, const Context &context) {
if (_switched) {
_wrapped->paint(p, context);
} else if (_wrapped->ready() && _strip->inDefaultState(_index)) {
_switched = true;
_wrapped->paint(p, context);
} else {
_strip->paintOne(p, _index, context.position + _shift, 1.);
}
}
void StripEmoji::unload() {
_wrapped->unload();
_switched = true;
}
bool StripEmoji::ready() {
return _wrapped->ready();
}
} // namespace
Selector::Selector(
@ -275,7 +337,6 @@ void Selector::paintCollapsed(QPainter &p) {
void Selector::paintExpanding(Painter &p, float64 progress) {
const auto rects = paintExpandingBg(p, progress);
//paintStripWithoutExpand(p);
progress /= kFullDuration;
if (_footer) {
_footer->paintExpanding(
@ -334,16 +395,6 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
};
}
void Selector::paintStripWithoutExpand(QPainter &p) {
_strip.paint(
p,
_inner.topLeft() + QPoint(_skipx, _skipy),
{ _size, 0 },
_inner.marginsRemoved({ 0, 0, _skipx + _size, 0 }),
1.,
false);
}
void Selector::paintFadingExpandIcon(QPainter &p, float64 progress) {
if (progress >= 1.) {
return;
@ -365,7 +416,6 @@ void Selector::paintExpanded(QPainter &p) {
finishExpand();
}
p.drawImage(0, 0, _paintBuffer);
paintStripWithoutExpand(p);
}
void Selector::finishExpand() {
@ -429,6 +479,9 @@ int Selector::lookupSelectedIndex(QPoint position) const {
}
void Selector::setSelected(int index) {
if (index >= 0 && _expandScheduled) {
return;
}
_strip.setSelected(index);
const auto over = (index >= 0);
if (_over != over) {
@ -468,6 +521,10 @@ void Selector::mouseReleaseEvent(QMouseEvent *e) {
}
void Selector::expand() {
if (_expandScheduled) {
return;
}
_expandScheduled = true;
const auto parent = parentWidget()->geometry();
const auto additionalBottom = parent.height() - y() - height();
const auto additional = _specialExpandTopSkip + additionalBottom;
@ -483,11 +540,12 @@ void Selector::expand() {
cacheExpandIcon();
[[maybe_unused]] const auto grabbed = Ui::GrabWidget(_scroll);
setSelected(-1);
base::call_delayed(kExpandDelay, this, [=] {
_paintBuffer = _cachedRound.PrepareImage(size());
_expanded = true;
base::call_delayed(kExpandDelay, this, [this] {
const auto full = kExpandDuration + kScaleDuration;
_expanded = true;
_paintBuffer = _cachedRound.PrepareImage(size());
_expanding.start([=] { update(); }, 0., full, full);
});
}
@ -496,14 +554,7 @@ void Selector::cacheExpandIcon() {
_expandIconCache = _cachedRound.PrepareImage({ _size, _size });
_expandIconCache.fill(Qt::transparent);
auto q = QPainter(&_expandIconCache);
const auto count = _strip.count();
_strip.paint(
q,
QPoint(-(count - 1) * _size, 0),
{ _size, 0 },
QRect(-(count - 1) * _size, 0, count * _size, _size),
1.,
false);
_strip.paintOne(q, _strip.count() - 1, { 0, 0 }, 1.);
}
void Selector::createList(not_null<Window::SessionController*> controller) {
@ -511,6 +562,8 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
auto recent = std::vector<DocumentId>();
auto defaultReactionIds = base::flat_map<DocumentId, QString>();
recent.reserve(_reactions.recent.size());
auto index = 0;
const auto inStrip = _strip.count();
for (const auto &reaction : _reactions.recent) {
if (const auto id = reaction->id.custom()) {
recent.push_back(id);
@ -518,9 +571,12 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
recent.push_back(reaction->selectAnimation->id);
defaultReactionIds.emplace(recent.back(), reaction->id.emoji());
}
if (index + 1 < inStrip) {
_defaultReactionInStripMap.emplace(recent.back(), index++);
}
};
const auto manager = &controller->session().data().customEmojiManager();
const auto shift = [&] {
_stripPaintOneShift = [&] {
// See EmojiListWidget custom emoji position resolving.
const auto area = st::emojiPanArea;
const auto areaPosition = QPoint(
@ -533,19 +589,34 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
const auto customSkip = (esize - customSize) / 2;
const auto customPosition = QPoint(customSkip, customSkip);
return QPoint(
(_size - st::reactStripImage) / 2,
(_size - st::reactStripImage) / 2
) - areaPosition - innerPosition - customPosition;
return areaPosition + innerPosition + customPosition;
}();
auto factory = [=](DocumentId id, Fn<void()> repaint) {
return defaultReactionIds.contains(id)
_defaultReactionShift = QPoint(
(_size - st::reactStripImage) / 2,
(_size - st::reactStripImage) / 2
) - _stripPaintOneShift;
auto factory = [=](DocumentId id, Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto isDefaultReaction = defaultReactionIds.contains(id);
auto result = isDefaultReaction
? std::make_unique<ShiftedEmoji>(
manager,
id,
std::move(repaint),
shift)
: nullptr;
_defaultReactionShift)
: manager->create(
id,
std::move(repaint),
Data::CustomEmojiManager::SizeTag::Large);
const auto i = _defaultReactionInStripMap.find(id);
if (i != end(_defaultReactionInStripMap)) {
return std::make_unique<StripEmoji>(
std::move(result),
&_strip,
-_stripPaintOneShift,
i->second);
}
return result;
};
_scroll = Ui::CreateChild<Ui::ScrollArea>(this, st::reactPanelScroll);
_scroll->hide();
@ -631,6 +702,7 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
0,
0,
}));
_list->setMinimalHeight(geometry.width(), _scroll->height());
updateVisibleTopBottom();
}

View File

@ -83,7 +83,6 @@ private:
void paintCollapsed(QPainter &p);
void paintExpanding(Painter &p, float64 progress);
ExpandingRects paintExpandingBg(QPainter &p, float64 progress);
void paintStripWithoutExpand(QPainter &p);
void paintFadingExpandIcon(QPainter &p, float64 progress);
void paintExpanded(QPainter &p);
void paintBubble(QPainter &p, int innerWidth);
@ -100,7 +99,10 @@ private:
const base::weak_ptr<Window::SessionController> _parentController;
const Data::PossibleItemReactions _reactions;
base::flat_map<DocumentId, int> _defaultReactionInStripMap;
Ui::RoundAreaWithShadow _cachedRound;
QPoint _defaultReactionShift;
QPoint _stripPaintOneShift;
Strip _strip;
rpl::event_stream<ChosenReaction> _chosen;
@ -133,6 +135,7 @@ private:
bool _appearing = false;
bool _toggling = false;
bool _expanded = false;
bool _expandScheduled = false;
bool _expandFinished = false;
bool _small = false;
bool _over = false;

View File

@ -99,6 +99,28 @@ void Strip::paint(
const auto animationRect = clip.marginsRemoved({ 0, skip, 0, skip });
PainterHighQualityEnabler hq(p);
const auto countTarget = resolveCountTargetMethod(scale);
for (auto &icon : _icons) {
const auto target = countTarget(icon).translated(position);
position += shift;
if (target.intersects(clip)) {
paintOne(
p,
icon,
position - shift,
target,
!hiding && target.intersects(animationRect));
} else if (!hiding) {
clearStateForHidden(icon);
}
if (!hiding) {
clearStateForSelectFinished(icon);
}
}
}
auto Strip::resolveCountTargetMethod(float64 scale) const
-> Fn<QRectF(const ReactionIcons&)> {
const auto hoveredSize = int(base::SafeRound(_finalSize * kHoverScale));
const auto basicTargetForScale = [&](int size, float64 scale) {
const auto remove = size * (1. - scale) / 2.;
@ -110,7 +132,7 @@ void Strip::paint(
)).marginsRemoved({ remove, remove, remove, remove });
};
const auto basicTarget = basicTargetForScale(_finalSize, scale);
const auto countTarget = [&](const ReactionIcons &icon) {
return [=](const ReactionIcons &icon) {
const auto selectScale = icon.selectedScale.value(
icon.selected ? kHoverScale : 1.);
if (selectScale == 1.) {
@ -121,45 +143,62 @@ void Strip::paint(
? basicTargetForScale(_finalSize, finalScale)
: basicTargetForScale(hoveredSize, finalScale / kHoverScale);
};
for (auto &icon : _icons) {
const auto target = countTarget(icon).translated(position);
position += shift;
}
void Strip::paintOne(
QPainter &p,
ReactionIcons &icon,
QPoint position,
QRectF target,
bool allowAppearStart) {
if (icon.added == AddedButton::Premium) {
paintPremiumIcon(p, position, target);
} else if (icon.added == AddedButton::Expand) {
paintExpandIcon(p, position, target);
} else {
const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
const auto size = int(std::floor(target.width() + 0.01));
const auto frame = animation->frame({ size, size }, _update);
p.drawImage(target, frame.image);
};
if (!target.intersects(clip)) {
if (!hiding) {
clearStateForHidden(icon);
}
} else if (icon.added == AddedButton::Premium) {
paintPremiumIcon(p, position - shift, target);
} else if (icon.added == AddedButton::Expand) {
paintExpandIcon(p, position - shift, target);
} else {
const auto appear = icon.appear.get();
if (!hiding
&& appear
&& !icon.appearAnimated
&& target.intersects(animationRect)) {
icon.appearAnimated = true;
appear->animate(_update, 0, appear->framesCount() - 1);
}
if (appear && appear->animating()) {
paintFrame(appear);
} else if (const auto select = icon.select.get()) {
paintFrame(select);
}
const auto appear = icon.appear.get();
if (appear && !icon.appearAnimated && allowAppearStart) {
icon.appearAnimated = true;
appear->animate(_update, 0, appear->framesCount() - 1);
}
if (!hiding) {
clearStateForSelectFinished(icon);
if (appear && appear->animating()) {
paintFrame(appear);
} else if (const auto select = icon.select.get()) {
paintFrame(select);
}
}
}
void Strip::paintOne(
QPainter &p,
int index,
QPoint position,
float64 scale) {
Expects(index >= 0 && index < _icons.size());
auto &icon = _icons[index];
const auto countTarget = resolveCountTargetMethod(scale);
const auto target = countTarget(icon).translated(position);
paintOne(p, icon, position, target, false);
}
bool Strip::inDefaultState(int index) const {
Expects(index >= 0 && index < _icons.size());
const auto &icon = _icons[index];
return !icon.selected
&& !icon.selectedScale.animating()
&& icon.select
&& !icon.select->animating()
&& (!icon.appear || !icon.appear->animating());
}
bool Strip::empty() const {
return _icons.empty();
}

View File

@ -61,6 +61,8 @@ public:
QRect clip,
float64 scale,
bool hiding);
void paintOne(QPainter &p, int index, QPoint position, float64 scale);
[[nodiscard]] bool inDefaultState(int index) const;
[[nodiscard]] bool empty() const;
[[nodiscard]] int count() const;
@ -107,6 +109,14 @@ private:
[[nodiscard]] bool checkIconLoaded(ReactionDocument &entry) const;
void loadIcons();
void checkIcons();
void paintOne(
QPainter &p,
ReactionIcons &icon,
QPoint position,
QRectF target,
bool allowAppearStart);
[[nodiscard]] Fn<QRectF(const ReactionIcons&)> resolveCountTargetMethod(
float64 scale) const;
void resolveMainReactionIcon();
void setMainReactionIcon();

View File

@ -520,6 +520,10 @@ std::unique_ptr<Loader> Renderer::cancel() {
return _loader();
}
bool Renderer::canMakePreview() const {
return _cache.frames() > 0;
}
Preview Renderer::makePreview() const {
return _cache.makePreview();
}
@ -567,7 +571,7 @@ void Loading::paint(QPainter &p, const Context &context) {
}
bool Loading::hasImagePreview() const {
return _preview.isImage();
return _preview.isImage();
}
Preview Loading::imagePreview() const {
@ -599,84 +603,99 @@ Instance::Instance(
}
QString Instance::entityData() const {
if (const auto loading = std::get_if<Loading>(&_state)) {
return loading->entityData();
} else if (const auto caching = std::get_if<Caching>(&_state)) {
return caching->entityData;
} else if (const auto cached = std::get_if<Cached>(&_state)) {
return cached->entityData();
}
Unexpected("State in Instance::entityData.");
return v::match(_state, [](const Loading &state) {
return state.entityData();
}, [](const Caching &state) {
return state.entityData;
}, [](const Cached &state) {
return state.entityData();
});
}
void Instance::paint(QPainter &p, const Context &context) {
if (const auto loading = std::get_if<Loading>(&_state)) {
loading->paint(p, context);
loading->load([=](Loader::LoadResult result) {
if (auto caching = std::get_if<Caching>(&result)) {
caching->renderer->setRepaintCallback([=] { repaint(); });
_state = std::move(*caching);
} else if (auto cached = std::get_if<Cached>(&result)) {
_state = std::move(*cached);
repaint();
} else {
Unexpected("Value in Loader::LoadResult.");
}
});
} else if (const auto caching = std::get_if<Caching>(&_state)) {
auto result = caching->renderer->paint(p, context);
v::match(_state, [&](Loading &state) {
state.paint(p, context);
load(state);
}, [&](Caching &state) {
auto result = state.renderer->paint(p, context);
if (!result.painted) {
caching->preview.paint(p, context);
state.preview.paint(p, context);
} else {
if (!caching->preview.isExactImage()) {
caching->preview = caching->renderer->makePreview();
if (!state.preview.isExactImage()) {
state.preview = state.renderer->makePreview();
}
if (result.next > context.now) {
_repaintLater(this, { result.next, result.duration });
}
}
if (auto cached = caching->renderer->ready(caching->entityData)) {
if (auto cached = state.renderer->ready(state.entityData)) {
_state = std::move(*cached);
}
} else if (const auto cached = std::get_if<Cached>(&_state)) {
const auto result = cached->paint(p, context);
}, [&](Cached &state) {
const auto result = state.paint(p, context);
if (result.next > context.now) {
_repaintLater(this, { result.next, result.duration });
}
}
});
}
bool Instance::ready() {
return v::match(_state, [&](Loading &state) {
if (state.hasImagePreview()) {
return true;
}
load(state);
return false;
}, [](Caching &state) {
return state.renderer->canMakePreview();
}, [](Cached &state) {
return true;
});
}
void Instance::load(Loading &state) {
state.load([=](Loader::LoadResult result) {
if (auto caching = std::get_if<Caching>(&result)) {
caching->renderer->setRepaintCallback([=] { repaint(); });
_state = std::move(*caching);
} else if (auto cached = std::get_if<Cached>(&result)) {
_state = std::move(*cached);
repaint();
} else {
Unexpected("Value in Loader::LoadResult.");
}
});
}
bool Instance::hasImagePreview() const {
if (const auto loading = std::get_if<Loading>(&_state)) {
return loading->hasImagePreview();
} else if (const auto caching = std::get_if<Caching>(&_state)) {
return caching->preview.isImage();
} else if (const auto cached = std::get_if<Cached>(&_state)) {
return v::match(_state, [](const Loading &state) {
return state.hasImagePreview();
}, [](const Caching &state) {
return state.preview.isImage();
}, [](const Cached &state) {
return true;
}
return false;
});
}
Preview Instance::imagePreview() const {
if (const auto loading = std::get_if<Loading>(&_state)) {
return loading->imagePreview();
} else if (const auto caching = std::get_if<Caching>(&_state)) {
return caching->preview.isImage() ? caching->preview : Preview();
} else if (const auto cached = std::get_if<Cached>(&_state)) {
return cached->makePreview();
}
return {};
return v::match(_state, [](const Loading &state) {
return state.imagePreview();
}, [](const Caching &state) {
return state.preview.isImage() ? state.preview : Preview();
}, [](const Cached &state) {
return state.makePreview();
});
}
void Instance::updatePreview(Preview preview) {
if (const auto loading = std::get_if<Loading>(&_state)) {
loading->updatePreview(std::move(preview));
} else if (const auto caching = std::get_if<Caching>(&_state)) {
if ((!caching->preview.isImage() && preview.isImage())
|| (!caching->preview && preview)) {
caching->preview = std::move(preview);
v::match(_state, [&](Loading &state) {
state.updatePreview(std::move(preview));
}, [&](Caching &state) {
if ((!state.preview.isImage() && preview.isImage())
|| (!state.preview && preview)) {
state.preview = std::move(preview);
}
}
}, [](const Cached &) {});
}
void Instance::repaint() {
@ -694,16 +713,16 @@ void Instance::decrementUsage(not_null<Object*> object) {
if (!_usage.empty()) {
return;
}
if (const auto loading = std::get_if<Loading>(&_state)) {
loading->cancel();
} else if (const auto caching = std::get_if<Caching>(&_state)) {
v::match(_state, [](Loading &state) {
state.cancel();
}, [&](Caching &state) {
_state = Loading{
caching->renderer->cancel(),
std::move(caching->preview),
state.renderer->cancel(),
std::move(state.preview),
};
} else if (const auto cached = std::get_if<Cached>(&_state)) {
_state = cached->unload();
}
}, [&](Cached &state) {
_state = state.unload();
});
_repaintLater(this, RepaintRequest());
}
@ -735,6 +754,14 @@ void Object::unload() {
}
}
bool Object::ready() {
if (!_using) {
_using = true;
_instance->incrementUsage(this);
}
return _instance->ready();
}
void Object::repaint() {
_repaint();
}

View File

@ -145,6 +145,7 @@ public:
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
[[nodiscard]] std::unique_ptr<Loader> cancel();
[[nodiscard]] bool canMakePreview() const;
[[nodiscard]] Preview makePreview() const;
void setRepaintCallback(Fn<void()> repaint);
@ -223,6 +224,7 @@ public:
[[nodiscard]] QString entityData() const;
void paint(QPainter &p, const Context &context);
[[nodiscard]] bool ready();
[[nodiscard]] bool hasImagePreview() const;
[[nodiscard]] Preview imagePreview() const;
void updatePreview(Preview preview);
@ -233,6 +235,8 @@ public:
void repaint();
private:
void load(Loading &state);
std::variant<Loading, Caching, Cached> _state;
base::flat_set<not_null<Object*>> _usage;
Fn<void(not_null<Instance*> that, RepaintRequest)> _repaintLater;
@ -253,6 +257,7 @@ public:
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
bool ready() override;
void repaint();

@ -1 +1 @@
Subproject commit 01c4ba869a07eabc9eea2b633542a53e9ff6ff4c
Subproject commit fc2c55367099ca7bdeacd0d52ff6007f00a6ba72