Implement expanding of list / categories.

This commit is contained in:
John Preston 2022-08-23 14:15:14 +03:00
parent c5fa4aae62
commit 20d4d00634
6 changed files with 224 additions and 47 deletions

View File

@ -483,6 +483,21 @@ auto EmojiListWidget::premiumChosen() const
return _premiumChosen.events();
}
void EmojiListWidget::paintExpanding(
QPainter &p,
QRect clip,
RectPart origin) {
const auto shift = clip.topLeft();
const auto adjusted = clip.translated(-shift);
p.translate(shift);
p.setClipRect(adjusted);
const auto context = ExpandingContext{
.expanding = true,
};
paint(p, context, adjusted);
p.translate(-shift);
}
void EmojiListWidget::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
@ -755,14 +770,26 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
_repaintsScheduled.clear();
const auto r = e ? e->rect() : rect();
if (r != rect()) {
p.setClipRect(r);
}
p.fillRect(r, st::emojiPanBg);
const auto clip = e ? e->rect() : rect();
p.fillRect(clip, st::emojiPanBg);
auto fromColumn = floorclamp(r.x() - _rowsLeft, _singleSize.width(), 0, _columnCount);
auto toColumn = ceilclamp(r.x() + r.width() - _rowsLeft, _singleSize.width(), 0, _columnCount);
paint(p, {}, clip);
}
void EmojiListWidget::paint(
QPainter &p,
const ExpandingContext &context,
QRect clip) {
auto fromColumn = floorclamp(
clip.x() - _rowsLeft,
_singleSize.width(),
0,
_columnCount);
auto toColumn = ceilclamp(
clip.x() + clip.width() - _rowsLeft,
_singleSize.width(),
0,
_columnCount);
if (rtl()) {
qSwap(fromColumn, toColumn);
fromColumn = _columnCount - fromColumn;
@ -775,9 +802,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
? &_pressed
: &_selected);
enumerateSections([&](const SectionInfo &info) {
if (r.top() >= info.rowsBottom) {
if (clip.top() >= info.rowsBottom) {
return true;
} else if (r.top() + r.height() <= info.top) {
} else if (clip.top() + clip.height() <= info.top) {
return false;
}
const auto buttonSelected = selectedButton
@ -785,8 +812,8 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
: false;
const auto widthForTitle = emojiRight()
- (st().headerLeft - st().margin.left())
- paintButtonGetWidth(p, info, buttonSelected, r);
if (info.section > 0 && r.top() < info.rowsTop) {
- paintButtonGetWidth(p, info, buttonSelected, clip);
if (info.section > 0 && clip.top() < info.rowsTop) {
p.setFont(st::emojiPanHeaderFont);
p.setPen(st::emojiPanHeaderFg);
auto titleText = (info.section < _staticCount)
@ -808,14 +835,23 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
top,
width());
}
const auto textTop = top + st::emojiPanHeaderFont->ascent;
p.setFont(st::emojiPanHeaderFont);
p.setPen(st::emojiPanHeaderFg);
p.drawTextLeft(left, top, width(), titleText, titleWidth);
p.drawText(left, textTop, titleText);
}
if (r.top() + r.height() > info.rowsTop) {
if (clip.top() + clip.height() > info.rowsTop) {
ensureLoaded(info.section);
auto fromRow = floorclamp(r.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
auto toRow = ceilclamp(r.y() + r.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
auto fromRow = floorclamp(
clip.y() - info.rowsTop,
_singleSize.height(),
0,
info.rowsCount);
auto toRow = ceilclamp(
clip.y() + clip.height() - info.rowsTop,
_singleSize.height(),
0,
info.rowsCount);
for (auto i = fromRow; i < toRow; ++i) {
for (auto j = fromColumn; j < toColumn; ++j) {
const auto index = i * _columnCount + j;
@ -1790,6 +1826,7 @@ QPoint EmojiListWidget::buttonRippleTopLeft(int section) const {
void EmojiListWidget::refreshEmoji() {
refreshRecent();
refreshCustom();
resizeToWidth(width());
}
void EmojiListWidget::showSet(uint64 setId) {

View File

@ -112,6 +112,8 @@ public:
[[nodiscard]] auto premiumChosen() const
-> rpl::producer<not_null<DocumentData*>>;
void paintExpanding(QPainter &p, QRect clip, RectPart origin);
protected:
void visibleTopBottomUpdated(
int visibleTop,
@ -205,6 +207,9 @@ private:
OverEmoji,
OverSet,
OverButton>;
struct ExpandingContext {
bool expanding = false;
};
template <typename Callback>
bool enumerateSections(Callback callback) const;
@ -230,6 +235,7 @@ private:
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
void selectEmoji(EmojiPtr emoji);
void selectCustom(not_null<DocumentData*> document);
void paint(QPainter &p, const ExpandingContext &context, QRect clip);
void drawCollapsedBadge(QPainter &p, QPoint position, int count);
void drawRecent(
QPainter &p,

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/buttons.h"
#include "ui/rect_part.h"
#include "styles/style_chat_helpers.h"
#include <QtWidgets/QApplication>
@ -222,6 +223,27 @@ void StickersListFooter::clearHeavyData() {
});
}
void StickersListFooter::paintExpanding(
Painter &p,
QRect clip,
float64 radius,
RectPart origin) {
const auto delta = ((origin | RectPart::None) & RectPart::FullBottom)
? (height() - clip.height())
: 0;
const auto shift = QPoint(clip.x(), clip.y() - delta);
p.translate(shift);
const auto context = ExpandingContext{
.clip = clip.translated(-shift),
.progress = clip.height() / float64(height()),
.radius = int(std::ceil(radius)),
.expanding = true,
};
paint(p, context);
p.translate(-shift);
p.setClipping(false);
}
void StickersListFooter::initSearch() {
_searchField.create(
this,
@ -536,9 +558,15 @@ void StickersListFooter::setLoading(bool loading) {
}
void StickersListFooter::paintEvent(QPaintEvent *e) {
Painter p(this);
auto p = Painter(this);
_repaintScheduled = false;
paint(p, {});
}
void StickersListFooter::paint(
Painter &p,
const ExpandingContext &context) const {
if (_searchButtonVisible) {
paintSearchIcon(p);
}
@ -558,25 +586,37 @@ void StickersListFooter::paintEvent(QPaintEvent *e) {
if (rtl()) {
clip.moveLeft(width() - _iconsLeft - clip.width());
}
p.setClipRect(clip);
if (context.expanding) {
const auto both = clip.intersected(
context.clip.marginsRemoved(
{ context.radius, 0, context.radius, 0 }));
if (both.isEmpty()) {
return;
}
p.setClipRect(both);
} else {
p.setClipRect(clip);
}
if (!_barSelection) {
paintSelectionBg(p);
paintSelectionBg(p, context);
}
const auto now = crl::now();
const auto paused = _paused();
enumerateVisibleIcons([&](const IconInfo &info) {
paintSetIcon(p, info, now, paused);
paintSetIcon(p, context, info, now, paused);
});
if (_barSelection) {
paintSelectionBar(p);
}
paintLeftRightFading(p);
paintLeftRightFading(p, context);
}
void StickersListFooter::paintSelectionBg(Painter &p) const {
void StickersListFooter::paintSelectionBg(
QPainter &p,
const ExpandingContext &context) const {
auto selxrel = _iconsLeft + qRound(_iconState.selectionX.current());
auto selx = selxrel - qRound(_iconState.x.current());
const auto selw = qRound(_iconState.selectionWidth.current());
@ -585,9 +625,18 @@ void StickersListFooter::paintSelectionBg(Painter &p) const {
}
const auto sely = _iconsTop;
const auto area = st().iconArea;
const auto rect = QRect(
auto rect = QRect(
QPoint(selx, sely) + _areaPosition,
QSize(selw - 2 * _areaPosition.x(), area));
if (context.expanding) {
const auto recthalf = rect.height() / 2;
const auto myhalf = height() / 2;
const auto sub = anim::interpolate(recthalf, 0, context.progress);
const auto shift = anim::interpolate(myhalf, 0, context.progress);
rect = rect.marginsRemoved(
{ sub, sub, sub, sub }
).translated(0, shift);
}
if (rect.width() == rect.height() || _subiconsWidth <= _singleWidth) {
_selectionBg.paint(p, rect);
} else if (selw == _subiconsWidth) {
@ -606,7 +655,7 @@ void StickersListFooter::paintSelectionBg(Painter &p) const {
}
}
void StickersListFooter::paintSelectionBar(Painter &p) const {
void StickersListFooter::paintSelectionBar(QPainter &p) const {
auto selxrel = _iconsLeft + qRound(_iconState.selectionX.current());
auto selx = selxrel - qRound(_iconState.x.current());
const auto selw = qRound(_iconState.selectionWidth.current());
@ -621,26 +670,37 @@ void StickersListFooter::paintSelectionBar(Painter &p) const {
st::stickerIconSelColor);
}
void StickersListFooter::paintLeftRightFading(Painter &p) const {
auto o_left = std::clamp(
void StickersListFooter::paintLeftRightFading(
QPainter &p,
const ExpandingContext &context) const {
const auto o_left_normal = std::clamp(
_iconState.x.current() / st().fadeLeft.width(),
0.,
1.);
const auto o_left = context.expanding
? (1. - context.progress * (1. - o_left_normal))
: o_left_normal;
const auto radiusSkip = context.expanding
? std::max(context.radius - st::roundRadiusSmall, 0)
: 0;
if (o_left > 0) {
p.setOpacity(o_left);
st().fadeLeft.fill(p, style::rtlrect(_iconsLeft, _iconsTop, st().fadeLeft.width(), st().footer, width()));
st().fadeLeft.fill(p, style::rtlrect(std::max(_iconsLeft, radiusSkip), _iconsTop, st().fadeLeft.width(), st().footer, width()));
p.setOpacity(1.);
}
auto o_right = std::clamp(
const auto o_right_normal = std::clamp(
(_iconState.max - _iconState.x.current()) / st().fadeRight.width(),
0.,
1.);
const auto o_right = context.expanding
? (1. - context.progress * (1. - o_right_normal))
: o_right_normal;
if (o_right > 0) {
p.setOpacity(o_right);
st().fadeRight.fill(
p,
style::rtlrect(
width() - _iconsRight - st().fadeRight.width(),
width() - std::max(_iconsRight, radiusSkip) - st().fadeRight.width(),
_iconsTop,
st().fadeRight.width(),
st().footer, width()));
@ -1043,7 +1103,7 @@ bool StickersListFooter::hasOnlyFeaturedSets() const {
&& (_icons[0].setId == Data::Stickers::FeaturedSetId);
}
void StickersListFooter::paintStickerSettingsIcon(Painter &p) const {
void StickersListFooter::paintStickerSettingsIcon(QPainter &p) const {
const auto settingsLeft = width() - _iconsRight;
st::stickersSettings.paint(
p,
@ -1053,7 +1113,7 @@ void StickersListFooter::paintStickerSettingsIcon(Painter &p) const {
width());
}
void StickersListFooter::paintSearchIcon(Painter &p) const {
void StickersListFooter::paintSearchIcon(QPainter &p) const {
const auto searchLeft = _iconsLeft - _singleWidth;
st::stickersSearch.paint(
p,
@ -1152,10 +1212,21 @@ void StickersListFooter::updateSetIconAt(int left) {
void StickersListFooter::paintSetIcon(
Painter &p,
const ExpandingContext &context,
const IconInfo &info,
crl::time now,
bool paused) const {
const auto &icon = _icons[info.index];
if (context.expanding) {
p.save();
const auto center = QPoint(
info.adjustedLeft + _singleWidth / 2,
_iconsTop + st().footer / 2);
const auto shift = QPoint(0, anim::interpolate(height() / 2, 0, context.progress));
p.translate(shift + center);
p.scale(context.progress, context.progress);
p.translate(-center);
}
if (icon.sticker) {
icon.ensureMediaCreated();
const_cast<StickersListFooter*>(this)->validateIconAnimation(icon);
@ -1296,6 +1367,9 @@ void StickersListFooter::paintSetIcon(
}());
}
}
if (context.expanding) {
p.restore();
}
}
LocalStickersManager::LocalStickersManager(not_null<Main::Session*> session)

View File

@ -134,6 +134,12 @@ public:
};
[[nodiscard]] rpl::producer<SearchRequest> searchRequests() const;
void paintExpanding(
Painter &p,
QRect clip,
float64 radius,
RectPart origin);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
@ -182,6 +188,12 @@ private:
crl::time animationStart = 0;
Ui::Animations::Basic animation;
};
struct ExpandingContext {
QRect clip;
float64 progress = 0.;
int radius = 0;
bool expanding = false;
};
void enumerateVisibleIcons(Fn<void(const IconInfo &)> callback) const;
void enumerateIcons(Fn<bool(const IconInfo &)> callback) const;
@ -212,16 +224,23 @@ private:
void checkDragging(ScrollState &state);
bool finishDragging(ScrollState &state);
bool finishDragging();
void paintStickerSettingsIcon(Painter &p) const;
void paintSearchIcon(Painter &p) const;
void paint(Painter &p, const ExpandingContext &context) const;
void paintStickerSettingsIcon(QPainter &p) const;
void paintSearchIcon(QPainter &p) const;
void paintSetIcon(
Painter &p,
const ExpandingContext &context,
const IconInfo &info,
crl::time now,
bool paused) const;
void paintSelectionBg(Painter &p) const;
void paintSelectionBar(Painter &p) const;
void paintLeftRightFading(Painter &p) const;
void paintSelectionBg(
QPainter &p,
const ExpandingContext &context) const;
void paintSelectionBar(QPainter &p) const;
void paintLeftRightFading(
QPainter &p,
const ExpandingContext &context) const;
void updateEmojiSectionWidth();
void updateEmojiWidthCallback();

View File

@ -267,13 +267,25 @@ void Selector::paintCollapsed(QPainter &p) {
false);
}
void Selector::paintExpanding(QPainter &p, float64 progress) {
paintExpandingBg(p, progress);
paintStripWithoutExpand(p);
void Selector::paintExpanding(Painter &p, float64 progress) {
const auto rects = paintExpandingBg(p, progress);
//paintStripWithoutExpand(p);
paintFadingExpandIcon(p, progress);
if (_footer) {
_footer->paintExpanding(
p,
rects.categories,
rects.radius,
RectPart::BottomRight);
}
_list->paintExpanding(
p,
rects.list.marginsRemoved(st::reactPanelEmojiPan.margin),
RectPart::TopRight);
}
void Selector::paintExpandingBg(QPainter &p, float64 progress) {
auto Selector::paintExpandingBg(QPainter &p, float64 progress)
-> ExpandingRects {
constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount;
const auto frame = int(base::SafeRound(progress * (kFramesCount - 1)));
const auto radiusStart = st::reactStripHeight / 2.;
@ -292,6 +304,21 @@ void Selector::paintExpandingBg(QPainter &p, float64 progress) {
if (!fill.isEmpty()) {
p.fillRect(fill, st::defaultPopupMenu.menu.itemBg);
}
const auto categories = anim::interpolate(
0,
extendTopForCategories(),
expanding);
const auto inner = outer.marginsRemoved(extents);
_shadowTop = inner.y() + categories;
_shadowSkip = (categories < radius)
? int(base::SafeRound(
radius - sqrt(categories * (2 * radius - categories))))
: 0;
return {
.categories = QRect(inner.x(), inner.y(), inner.width(), categories),
.list = inner.marginsRemoved({ 0, categories, 0, 0 }),
.radius = radius,
};
}
void Selector::paintStripWithoutExpand(QPainter &p) {
@ -317,6 +344,7 @@ void Selector::paintFadingExpandIcon(QPainter &p, float64 progress) {
QSize(_size, _size)
).marginsRemoved({ sub, sub, sub, sub });
p.drawImage(expandIconRect, _expandIconCache);
p.setOpacity(1.);
}
void Selector::paintExpanded(QPainter &p) {
@ -360,7 +388,7 @@ void Selector::paintBubble(QPainter &p, int innerWidth) {
}
void Selector::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
auto p = Painter(this);
if (_appearing) {
paintAppearing(p);
} else if (!_expanded) {
@ -543,12 +571,17 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
inner.y(),
inner.width(),
_footer->height());
_shadowTop = _outer.y();
_shadowSkip = (st::reactStripHeight / 2);
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
_footer->geometryValue() | rpl::start_with_next([=](QRect geometry) {
rpl::combine(
_shadowTop.value(),
_shadowSkip.value()
) | rpl::start_with_next([=](int top, int skip) {
shadow->setGeometry(
geometry.x(),
geometry.y() + geometry.height(),
geometry.width(),
inner.x() + skip,
top,
inner.width() - 2 * skip,
st::lineWidth);
}, shadow->lifetime());
shadow->show();
@ -556,8 +589,8 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
const auto geometry = inner.marginsRemoved(
st::reactPanelEmojiPan.margin);
_list->move(0, 0);
_list->refreshEmoji();
_list->resizeToWidth(geometry.width());
_list->refreshEmoji();
_list->show();
const auto updateVisibleTopBottom = [=] {

View File

@ -66,6 +66,12 @@ public:
private:
static constexpr int kFramesCount = 32;
struct ExpandingRects {
QRect categories;
QRect list;
float64 radius = 0.;
};
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
@ -74,8 +80,8 @@ private:
void paintAppearing(QPainter &p);
void paintCollapsed(QPainter &p);
void paintExpanding(QPainter &p, float64 progress);
void paintExpandingBg(QPainter &p, float64 progress);
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);
@ -101,6 +107,8 @@ private:
Ui::ScrollArea *_scroll = nullptr;
ChatHelpers::EmojiListWidget *_list = nullptr;
ChatHelpers::StickersListFooter *_footer = nullptr;
rpl::variable<int> _shadowTop = 0;
rpl::variable<int> _shadowSkip = 0;
QImage _paintBuffer;
Ui::Animations::Simple _expanding;