From 20d4d00634ee8be963027d5e5a0fbeb99a969d2c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 23 Aug 2022 14:15:14 +0300 Subject: [PATCH] Implement expanding of list / categories. --- .../chat_helpers/emoji_list_widget.cpp | 67 ++++++++--- .../chat_helpers/emoji_list_widget.h | 6 + .../chat_helpers/stickers_list_footer.cpp | 104 +++++++++++++++--- .../chat_helpers/stickers_list_footer.h | 29 ++++- .../history_view_reactions_selector.cpp | 53 +++++++-- .../history_view_reactions_selector.h | 12 +- 6 files changed, 224 insertions(+), 47 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 2d2ddd5f68..ec82d7aa67 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -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) { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index c7a86be04d..b8a35e9cca 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -112,6 +112,8 @@ public: [[nodiscard]] auto premiumChosen() const -> rpl::producer>; + 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 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 document); + void paint(QPainter &p, const ExpandingContext &context, QRect clip); void drawCollapsedBadge(QPainter &p, QPoint position, int count); void drawRecent( QPainter &p, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 65ae21ad82..d53272ac82 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -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 @@ -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(this)->validateIconAnimation(icon); @@ -1296,6 +1367,9 @@ void StickersListFooter::paintSetIcon( }()); } } + if (context.expanding) { + p.restore(); + } } LocalStickersManager::LocalStickersManager(not_null session) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index e1f8bb75ce..e4fd909a7f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -134,6 +134,12 @@ public: }; [[nodiscard]] rpl::producer 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 callback) const; void enumerateIcons(Fn 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(); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 9d9fe71639..573f8036a3 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -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 controller) { inner.y(), inner.width(), _footer->height()); + _shadowTop = _outer.y(); + _shadowSkip = (st::reactStripHeight / 2); const auto shadow = Ui::CreateChild(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 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 = [=] { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 7ea4cd965d..93a643454e 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -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 _shadowTop = 0; + rpl::variable _shadowSkip = 0; QImage _paintBuffer; Ui::Animations::Simple _expanding;