Implement nice emoji categories scrolling.

This commit is contained in:
John Preston 2023-01-24 12:11:09 +04:00
parent 04482ef33b
commit 4f18535f8d
10 changed files with 242 additions and 46 deletions

View file

@ -656,7 +656,7 @@ void MembersRow::paintComplexStatusText(
(_mutedByMe (_mutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now) ? tr::lng_group_call_muted_by_me_status(tr::now)
: !about.isEmpty() : !about.isEmpty()
? font->m.elidedText(about, Qt::ElideRight, availableWidth) ? font->elided(about, availableWidth)
: _delegate->rowIsMe(peer()) : _delegate->rowIsMe(peer())
? tr::lng_status_connecting(tr::now) ? tr::lng_status_connecting(tr::now)
: tr::lng_group_call_invited_status(tr::now))); : tr::lng_group_call_invited_status(tr::now)));

View file

@ -24,6 +24,7 @@ TabbedSearch {
cancel: CrossButton; cancel: CrossButton;
defaultFieldWidth: pixels; defaultFieldWidth: pixels;
groupWidth: pixels; groupWidth: pixels;
groupSkip: pixels;
height: pixels; height: pixels;
} }
@ -248,8 +249,9 @@ defaultTabbedSearch: TabbedSearch {
loadingPeriod: 1000; loadingPeriod: 1000;
ripple: emptyRippleAnimation; ripple: emptyRippleAnimation;
} }
defaultFieldWidth: 101px; defaultFieldWidth: 103px;
groupWidth: 30px; groupWidth: 30px;
groupSkip: 2px;
height: 33px; height: 33px;
} }
defaultEmojiPan: EmojiPan { defaultEmojiPan: EmojiPan {

View file

@ -476,7 +476,9 @@ void EmojiListWidget::setupSearch() {
.groups = (_mode == Mode::EmojiStatus .groups = (_mode == Mode::EmojiStatus
? session().data().emojiStatuses().statusGroupsValue() ? session().data().emojiStatuses().statusGroupsValue()
: session().data().emojiStatuses().emojiGroupsValue()), : session().data().emojiStatuses().emojiGroupsValue()),
.customEmojiFactory = session().data().customEmojiManager().factory() .customEmojiFactory = session().data().customEmojiManager().factory(
Data::CustomEmojiManager::SizeTag::SetIcon,
Ui::SearchWithGroups::IconSizeOverride())
}); });
_search->queryValue( _search->queryValue(
) | rpl::start_with_next([=](std::vector<QString> &&query) { ) | rpl::start_with_next([=](std::vector<QString> &&query) {

View file

@ -549,7 +549,6 @@ void StickersListFooter::processHideFinished() {
_subiconState.animation.stop(); _subiconState.animation.stop();
_subiconState.animationStart = 0; _subiconState.animationStart = 0;
_subiconState.x.finish(); _subiconState.x.finish();
_horizontal = false;
} }
void StickersListFooter::leaveToChildEvent(QEvent *e, QWidget *child) { void StickersListFooter::leaveToChildEvent(QEvent *e, QWidget *child) {
@ -943,9 +942,6 @@ void StickersListFooter::scrollByWheelEvent(
if (!horizontal && !vertical) { if (!horizontal && !vertical) {
return; return;
} }
if (horizontal) {
_horizontal = true;
}
auto delta = horizontal auto delta = horizontal
? ((rtl() ? -1 : 1) * (e->pixelDelta().x() ? ((rtl() ? -1 : 1) * (e->pixelDelta().x()
? e->pixelDelta().x() ? e->pixelDelta().x()

View file

@ -304,8 +304,6 @@ private:
bool _barSelection = false; bool _barSelection = false;
bool _repaintScheduled = false; bool _repaintScheduled = false;
bool _horizontal = false;
bool _searchShown = false; bool _searchShown = false;
object_ptr<Ui::InputField> _searchField = { nullptr }; object_ptr<Ui::InputField> _searchField = { nullptr };
object_ptr<Ui::CrossButton> _searchCancel = { nullptr }; object_ptr<Ui::CrossButton> _searchCancel = { nullptr };

View file

@ -285,7 +285,6 @@ void Widget::hideFinished() {
_a_show.stop(); _a_show.stop();
_showAnimation.reset(); _showAnimation.reset();
_cache = QPixmap(); _cache = QPixmap();
_horizontal = false;
_hiding = false; _hiding = false;
_scroll->scrollToY(0); _scroll->scrollToY(0);

View file

@ -129,7 +129,6 @@ private:
int _contentMaxHeight = 0; int _contentMaxHeight = 0;
int _contentHeight = 0; int _contentHeight = 0;
bool _horizontal = false;
int _width = 0; int _width = 0;
int _height = 0; int _height = 0;

View file

@ -15,10 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h" #include "ui/rect.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include <QtWidgets/QApplication>
namespace Ui { namespace Ui {
namespace { namespace {
constexpr auto kDebounceTimeout = crl::time(400); constexpr auto kDebounceTimeout = crl::time(400);
constexpr auto kCategoryIconSizeOverride = 22;
class GroupsStrip final : public RpWidget { class GroupsStrip final : public RpWidget {
public: public:
@ -28,23 +31,35 @@ public:
rpl::producer<std::vector<EmojiGroup>> groups, rpl::producer<std::vector<EmojiGroup>> groups,
Text::CustomEmojiFactory factory); Text::CustomEmojiFactory factory);
[[nodiscard]] rpl::producer<EmojiGroup> chosen() const; void scrollByWheel(QWheelEvent *e);
struct Chosen {
not_null<const EmojiGroup*> group;
int iconLeft = 0;
int iconRight = 0;
};
[[nodiscard]] rpl::producer<Chosen> chosen() const;
void clearChosen(); void clearChosen();
[[nodiscard]] rpl::producer<int> moveRequests() const;
private: private:
struct Button { struct Button {
EmojiGroup group; EmojiGroup group;
QString iconId; QString iconId;
std::unique_ptr<Ui::Text::CustomEmoji> icon; std::unique_ptr<Text::CustomEmoji> icon;
}; };
void init(rpl::producer<std::vector<EmojiGroup>> groups); void init(rpl::producer<std::vector<EmojiGroup>> groups);
void set(std::vector<EmojiGroup> list); void set(std::vector<EmojiGroup> list);
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
void fireChosenGroup();
static inline auto FindById(auto &&buttons, QStringView id) { static inline auto FindById(auto &&buttons, QStringView id) {
return ranges::find(buttons, id, &Button::iconId); return ranges::find(buttons, id, &Button::iconId);
} }
@ -53,8 +68,10 @@ private:
const Text::CustomEmojiFactory _factory; const Text::CustomEmojiFactory _factory;
std::vector<Button> _buttons; std::vector<Button> _buttons;
rpl::event_stream<EmojiGroup> _chosenGroup; rpl::event_stream<Chosen> _chosenGroup;
int _selected = -1; rpl::event_stream<int> _moveRequests;
QPoint _globalPressPoint, _globalLastPoint;
bool _dragging = false;
int _pressed = -1; int _pressed = -1;
int _chosen = -1; int _chosen = -1;
@ -78,10 +95,14 @@ GroupsStrip::GroupsStrip(
init(std::move(groups)); init(std::move(groups));
} }
rpl::producer<EmojiGroup> GroupsStrip::chosen() const { rpl::producer<GroupsStrip::Chosen> GroupsStrip::chosen() const {
return _chosenGroup.events(); return _chosenGroup.events();
} }
rpl::producer<int> GroupsStrip::moveRequests() const {
return _moveRequests.events();
}
void GroupsStrip::clearChosen() { void GroupsStrip::clearChosen() {
if (const auto chosen = std::exchange(_chosen, -1); chosen >= 0) { if (const auto chosen = std::exchange(_chosen, -1); chosen >= 0) {
update(); update();
@ -138,7 +159,7 @@ void GroupsStrip::set(std::vector<EmojiGroup> list) {
const auto i = FindById(_buttons, chosen); const auto i = FindById(_buttons, chosen);
if (i != end(_buttons)) { if (i != end(_buttons)) {
_chosen = (i - begin(_buttons)); _chosen = (i - begin(_buttons));
_chosenGroup.fire_copy(i->group); fireChosenGroup();
} else { } else {
_chosen = -1; _chosen = -1;
} }
@ -150,17 +171,22 @@ void GroupsStrip::paintEvent(QPaintEvent *e) {
auto p = QPainter(this); auto p = QPainter(this);
auto index = 0; auto index = 0;
const auto single = _st.groupWidth; const auto single = _st.groupWidth;
const auto skip = _st.groupSkip;
const auto height = this->height(); const auto height = this->height();
const auto clip = e->rect(); const auto clip = e->rect();
const auto now = crl::now(); const auto now = crl::now();
for (const auto &button : _buttons) { for (const auto &button : _buttons) {
const auto left = index * single; const auto left = index * single;
const auto top = 0; const auto top = 0;
const auto size = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); const auto size = SearchWithGroups::IconSizeOverride();
if (_chosen == index) { if (_chosen == index) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::windowBgRipple); p.setBrush(st::windowBgRipple);
p.drawEllipse(left, top + (height - single) / 2, single, single); p.drawEllipse(
left + skip,
top + (height - single) / 2 + skip,
single - 2 * skip,
single - 2 * skip);
} }
if (QRect(left, top, single, height).intersects(clip)) { if (QRect(left, top, single, height).intersects(clip)) {
button.icon->paint(p, { button.icon->paint(p, {
@ -175,27 +201,74 @@ void GroupsStrip::paintEvent(QPaintEvent *e) {
} }
} }
void GroupsStrip::scrollByWheel(QWheelEvent *e) {
auto horizontal = (e->angleDelta().x() != 0);
auto vertical = (e->angleDelta().y() != 0);
if (!horizontal && !vertical) {
return;
}
const auto delta = horizontal
? ((style::RightToLeft() ? -1 : 1) * (e->pixelDelta().x()
? e->pixelDelta().x()
: e->angleDelta().x()))
: (e->pixelDelta().y()
? e->pixelDelta().y()
: e->angleDelta().y());
_moveRequests.fire_copy(delta);
}
void GroupsStrip::mouseMoveEvent(QMouseEvent *e) {
const auto point = e->globalPos();
if (!_dragging) {
const auto distance = (point - _globalPressPoint).manhattanLength();
if (distance >= QApplication::startDragDistance()) {
_dragging = true;
_globalLastPoint = _globalPressPoint;
}
}
if (_dragging) {
const auto delta = (point - _globalLastPoint).x();
_globalLastPoint = point;
_moveRequests.fire_copy(delta);
}
}
void GroupsStrip::mousePressEvent(QMouseEvent *e) { void GroupsStrip::mousePressEvent(QMouseEvent *e) {
const auto index = e->pos().x() / _st.groupWidth; const auto index = e->pos().x() / _st.groupWidth;
const auto chosen = (index < 0 || index >= _buttons.size()) const auto chosen = (index < 0 || index >= _buttons.size())
? -1 ? -1
: index; : index;
_pressed = chosen; _pressed = chosen;
_globalPressPoint = e->globalPos();
} }
void GroupsStrip::mouseReleaseEvent(QMouseEvent *e) { void GroupsStrip::mouseReleaseEvent(QMouseEvent *e) {
const auto pressed = std::exchange(_pressed, -1);
if (_dragging) {
_dragging = false;
return;
}
const auto index = e->pos().x() / _st.groupWidth; const auto index = e->pos().x() / _st.groupWidth;
const auto chosen = (index < 0 || index >= _buttons.size()) const auto chosen = (index < 0 || index >= _buttons.size())
? -1 ? -1
: index; : index;
const auto pressed = std::exchange(_pressed, -1);
if (pressed == index && index >= 0) { if (pressed == index && index >= 0) {
_chosen = pressed; _chosen = pressed;
_chosenGroup.fire_copy(_buttons[index].group); fireChosenGroup();
update(); update();
} }
} }
void GroupsStrip::fireChosenGroup() {
Expects(_chosen >= 0 && _chosen < _buttons.size());
_chosenGroup.fire({
.group = &_buttons[_chosen].group,
.iconLeft = _chosen * _st.groupWidth,
.iconRight = (_chosen + 1) * _st.groupWidth,
});
}
} // namespace } // namespace
SearchWithGroups::SearchWithGroups( SearchWithGroups::SearchWithGroups(
@ -218,17 +291,12 @@ SearchWithGroups::SearchWithGroups(
_st, _st,
std::move(descriptor.groups), std::move(descriptor.groups),
std::move(descriptor.customEmojiFactory)))) std::move(descriptor.customEmojiFactory))))
, _fadeLeft(CreateChild<FadeWrap<RpWidget>>( , _fade(CreateChild<RpWidget>(this))
this,
object_ptr<RpWidget>(this)))
, _fadeRight(CreateChild<FadeWrap<RpWidget>>(
this,
object_ptr<RpWidget>(this)))
, _debounceTimer([=] { _debouncedQuery = _query.current(); }) { , _debounceTimer([=] { _debouncedQuery = _query.current(); }) {
initField(); initField();
initGroups(); initGroups();
initEdges();
initButtons(); initButtons();
initEdges();
} }
anim::type SearchWithGroups::animated() const { anim::type SearchWithGroups::animated() const {
@ -266,7 +334,9 @@ void SearchWithGroups::initField() {
void SearchWithGroups::initGroups() { void SearchWithGroups::initGroups() {
const auto widget = static_cast<GroupsStrip*>(_groups->entity()); const auto widget = static_cast<GroupsStrip*>(_groups->entity());
_groups->move(_search->entity()->width() + _st.defaultFieldWidth, 0); const auto &search = _st.search;
_fadeLeftStart = search.iconPosition.x() + search.icon.width();
_groups->move(_fadeLeftStart + _st.defaultFieldWidth, 0);
widget->resize(widget->width(), _st.height); widget->resize(widget->width(), _st.height);
widget->widthValue( widget->widthValue(
) | rpl::filter([=] { ) | rpl::filter([=] {
@ -276,11 +346,17 @@ void SearchWithGroups::initGroups() {
}, widget->lifetime()); }, widget->lifetime());
widget->chosen( widget->chosen(
) | rpl::start_with_next([=](const EmojiGroup &group) { ) | rpl::start_with_next([=](const GroupsStrip::Chosen &chosen) {
_chosenGroup = group.iconId; _chosenGroup = chosen.group->iconId;
_query = group.emoticons; _query = chosen.group->emoticons;
_debouncedQuery = group.emoticons; _debouncedQuery = chosen.group->emoticons;
_debounceTimer.cancel(); _debounceTimer.cancel();
scrollGroupsToIcon(chosen.iconLeft, chosen.iconRight);
}, lifetime());
widget->moveRequests(
) | rpl::start_with_next([=](int delta) {
moveGroupsBy(width(), delta);
}, lifetime()); }, lifetime());
_chosenGroup.value( _chosenGroup.value(
@ -291,12 +367,47 @@ void SearchWithGroups::initGroups() {
_back->toggle(!empty, animated()); _back->toggle(!empty, animated());
if (empty) { if (empty) {
widget->clearChosen(); widget->clearChosen();
if (_field->getLastText().isEmpty()) {
_query = {};
_debouncedQuery = {};
_debounceTimer.cancel();
}
} else { } else {
_field->setText({}); _field->setText({});
} }
}, lifetime()); }, lifetime());
} }
void SearchWithGroups::scrollGroupsToIcon(int iconLeft, int iconRight) {
const auto single = _st.groupWidth;
const auto fadeRight = _fadeLeftStart + _st.fadeLeft.width();
if (_groups->x() < fadeRight + single - iconLeft) {
scrollGroupsTo(fadeRight + single - iconLeft);
} else if (_groups->x() > width() - single - iconRight) {
scrollGroupsTo(width() - single - iconRight);
} else {
_groupsLeftAnimation.stop();
}
}
void SearchWithGroups::scrollGroupsToStart() {
scrollGroupsTo(width());
}
void SearchWithGroups::scrollGroupsTo(int left) {
left = clampGroupsLeft(width(), left);
_groupsLeftTo = left;
const auto delta = _groupsLeftTo - _groups->x();
if (!delta) {
_groupsLeftAnimation.stop();
return;
}
_groupsLeftAnimation.start([=] {
const auto d = int(base::SafeRound(_groupsLeftAnimation.value(0)));
moveGroupsTo(width(), _groupsLeftTo - d);
}, delta, 0, st::slideWrapDuration, anim::sineInOut);
}
void SearchWithGroups::initEdges() { void SearchWithGroups::initEdges() {
paintRequest() | rpl::start_with_next([=](QRect clip) { paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(this).fillRect(clip, _st.bg); QPainter(this).fillRect(clip, _st.bg);
@ -305,6 +416,7 @@ void SearchWithGroups::initEdges() {
const auto makeEdge = [&](bool left) { const auto makeEdge = [&](bool left) {
const auto edge = CreateChild<RpWidget>(this); const auto edge = CreateChild<RpWidget>(this);
const auto size = QSize(height() / 2, height()); const auto size = QSize(height() / 2, height());
edge->setAttribute(Qt::WA_TransparentForMouseEvents);
edge->resize(size); edge->resize(size);
if (left) { if (left) {
edge->move(0, 0); edge->move(0, 0);
@ -329,6 +441,30 @@ void SearchWithGroups::initEdges() {
makeEdge(true); makeEdge(true);
makeEdge(false); makeEdge(false);
_fadeOpacity.changes(
) | rpl::start_with_next([=] {
_fade->update();
}, _fade->lifetime());
_fade->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(_fade);
p.setOpacity(_fadeOpacity.current());
const auto fill = QRect(0, 0, _fadeLeftStart, _st.height);
if (fill.intersects(clip)) {
p.fillRect(fill, _st.bg);
}
const auto icon = QRect(
_fadeLeftStart,
0,
_st.fadeLeft.width(),
_st.height);
if (clip.intersects(icon)) {
_st.fadeLeft.fill(p, icon);
}
}, _fade->lifetime());
_fade->setAttribute(Qt::WA_TransparentForMouseEvents);
style::PaletteChanged( style::PaletteChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_rounding = QImage(); _rounding = QImage();
@ -339,6 +475,22 @@ void SearchWithGroups::initButtons() {
_cancel->setClickedCallback([=] { _cancel->setClickedCallback([=] {
_field->setText(QString()); _field->setText(QString());
}); });
_back->entity()->setClickedCallback([=] {
_chosenGroup = QString();
scrollGroupsToStart();
});
_search->entity()->setClickedCallback([=] {
_field->setFocus();
scrollGroupsToStart();
});
QObject::connect(_field, &InputField::focused, [=] {
scrollGroupsToStart();
});
_field->raise();
_fade->raise();
_search->raise();
_back->raise();
_cancel->raise();
} }
void SearchWithGroups::ensureRounding(int size, float64 ratio) { void SearchWithGroups::ensureRounding(int size, float64 ratio) {
@ -366,19 +518,41 @@ auto SearchWithGroups::debouncedQueryValue() const
return _debouncedQuery.value(); return _debouncedQuery.value();
} }
int SearchWithGroups::IconSizeOverride() {
return style::ConvertScale(kCategoryIconSizeOverride);
}
int SearchWithGroups::resizeGetHeight(int newWidth) { int SearchWithGroups::resizeGetHeight(int newWidth) {
_back->moveToLeft(0, 0, newWidth); _back->moveToLeft(0, 0, newWidth);
_search->moveToLeft(0, 0, newWidth); _search->moveToLeft(0, 0, newWidth);
_cancel->moveToRight(0, 0, newWidth); _cancel->moveToRight(0, 0, newWidth);
const auto searchWidth = _search->entity()->width(); moveGroupsBy(newWidth, 0);
const auto groupsLeftDefault = searchWidth + _st.defaultFieldWidth;
const auto groupsLeftMin = newWidth - _groups->entity()->width(); const auto fadeWidth = _fadeLeftStart + _st.fadeLeft.width();
const auto fade = QRect(0, 0, fadeWidth, _st.height);
_fade->setGeometry(fade);
return _st.height;
}
void SearchWithGroups::wheelEvent(QWheelEvent *e) {
static_cast<GroupsStrip*>(_groups->entity())->scrollByWheel(e);
}
int SearchWithGroups::clampGroupsLeft(int width, int desiredLeft) const {
const auto groupsLeftDefault = _fadeLeftStart + _st.defaultFieldWidth;
const auto groupsLeftMin = width - _groups->entity()->width();
const auto groupsLeftMax = std::max(groupsLeftDefault, groupsLeftMin); const auto groupsLeftMax = std::max(groupsLeftDefault, groupsLeftMin);
const auto groupsLeft = std::clamp( return std::clamp(desiredLeft, groupsLeftMin, groupsLeftMax);
_groups->x(), }
groupsLeftMin,
groupsLeftMax); void SearchWithGroups::moveGroupsBy(int width, int delta) {
moveGroupsTo(width, _groups->x() + delta);
}
void SearchWithGroups::moveGroupsTo(int width, int to) {
const auto groupsLeft = clampGroupsLeft(width, to);
_groups->move(groupsLeft, 0); _groups->move(groupsLeft, 0);
const auto placeholderMargins = _st.field.textMargins const auto placeholderMargins = _st.field.textMargins
@ -388,12 +562,24 @@ int SearchWithGroups::resizeGetHeight(int newWidth) {
rect::m::sum::h(placeholderMargins) + placeholderWidth, rect::m::sum::h(placeholderMargins) + placeholderWidth,
_st.defaultFieldWidth); _st.defaultFieldWidth);
const auto fieldWidth = std::max( const auto fieldWidth = std::max(
groupsLeft - searchWidth, groupsLeft - _st.search.width,
fieldWidthMin); fieldWidthMin);
_field->resizeToWidth(fieldWidth); _field->resizeToWidth(fieldWidth);
_field->moveToLeft(groupsLeft - fieldWidth, 0); const auto fieldLeft = groupsLeft - fieldWidth;
_field->moveToLeft(fieldLeft, 0);
return _st.height; if (fieldLeft >= _fadeLeftStart) {
if (!_fade->isHidden()) {
_fade->hide();
}
} else {
if (_fade->isHidden()) {
_fade->show();
}
_fadeOpacity = (fieldLeft < _fadeLeftStart / 2)
? 1.
: (_fadeLeftStart - fieldLeft) / float64(_fadeLeftStart / 2);
}
} }
TabbedSearch::TabbedSearch( TabbedSearch::TabbedSearch(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
namespace style { namespace style {
@ -52,8 +53,18 @@ public:
[[nodiscard]] auto debouncedQueryValue() const [[nodiscard]] auto debouncedQueryValue() const
-> rpl::producer<std::vector<QString>>; -> rpl::producer<std::vector<QString>>;
[[nodiscard]] static int IconSizeOverride();
private: private:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
void wheelEvent(QWheelEvent *e) override;
[[nodiscard]] int clampGroupsLeft(int width, int desiredLeft) const;
void moveGroupsBy(int width, int delta);
void moveGroupsTo(int width, int to);
void scrollGroupsToIcon(int iconLeft, int iconRight);
void scrollGroupsToStart();
void scrollGroupsTo(int left);
[[nodiscard]] anim::type animated() const; [[nodiscard]] anim::type animated() const;
void initField(); void initField();
@ -69,10 +80,13 @@ private:
not_null<CrossButton*> _cancel; not_null<CrossButton*> _cancel;
not_null<InputField*> _field; not_null<InputField*> _field;
not_null<FadeWrap<RpWidget>*> _groups; not_null<FadeWrap<RpWidget>*> _groups;
not_null<FadeWrap<RpWidget>*> _fadeLeft; not_null<RpWidget*> _fade;
not_null<FadeWrap<RpWidget>*> _fadeRight; rpl::variable<float64> _fadeOpacity = 0.;
int _fadeLeftStart = 0;
rpl::variable<int> _fieldPlaceholderWidth; rpl::variable<int> _fieldPlaceholderWidth;
Ui::Animations::Simple _groupsLeftAnimation;
int _groupsLeftTo = 0;
QImage _rounding; QImage _rounding;

@ -1 +1 @@
Subproject commit 9cb928b7c41f0f1eb36a4d26ce31d053ea12466d Subproject commit 41ee2fb0f05908112c0cd132c7ab7168ba87fe0c