diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp index 6adde8927..46589c16b 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp @@ -633,7 +633,11 @@ void Manager::paintButton( if (expanded) { q->fillRect(QRect(QPoint(), size), context.st->windowBg()); } else { - const auto frame = _cachedRound.validateFrame(frameIndex, scale); + const auto radius = _inner.height() / 2.; + const auto frame = _cachedRound.validateFrame( + frameIndex, + scale, + radius); p.drawImage(position, *frame.image, frame.rect); } 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 c171057f9..b0bbac61f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -67,7 +67,7 @@ QMargins Selector::extentsForShadow() const { } int Selector::extendTopForCategories() const { - return st::emojiFooterHeight; + return _reactions.customAllowed ? st::emojiFooterHeight : 0; } int Selector::desiredHeight() const { @@ -76,6 +76,10 @@ int Selector::desiredHeight() const { : (_skipy + _recentRows * _size + _skipBottom); } +void Selector::setSpecialExpandTopSkip(int skip) { + _specialExpandTopSkip = skip; +} + void Selector::initGeometry(int innerTop) { const auto extents = extentsForShadow(); const auto parent = parentWidget()->rect(); @@ -84,10 +88,14 @@ void Selector::initGeometry(int innerTop) { const auto width = innerWidth + extents.left() + extents.right(); const auto height = innerHeight + extents.top() + extents.bottom(); const auto left = style::RightToLeft() ? 0 : (parent.width() - width); - const auto top = innerTop - extents.top(); + _collapsedTopSkip = extendTopForCategories() + _specialExpandTopSkip; + const auto top = innerTop - extents.top() - _collapsedTopSkip; const auto add = st::reactStripBubble.height() - extents.bottom(); - _outer = QRect(0, 0, width, height); - setGeometry(_outer.marginsAdded({ 0, 0, 0, add }).translated(left, top)); + _outer = QRect(0, _collapsedTopSkip, width, height); + _outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add }); + setGeometry(_outerWithBubble.marginsAdded( + { 0, _collapsedTopSkip, 0, 0 } + ).translated(left, top)); _inner = _outer.marginsRemoved(extents); } @@ -120,8 +128,8 @@ void Selector::paintAppearing(QPainter &p) { p.setOpacity(_appearOpacity); const auto factor = style::DevicePixelRatio(); - if (_paintBuffer.size() != size() * factor) { - _paintBuffer = _cachedRound.PrepareImage(size()); + if (_paintBuffer.size() != _outerWithBubble.size() * factor) { + _paintBuffer = _cachedRound.PrepareImage(_outerWithBubble.size()); } _paintBuffer.fill(st::defaultPopupMenu.menu.itemBg->c); auto q = QPainter(&_paintBuffer); @@ -133,16 +141,18 @@ void Selector::paintAppearing(QPainter &p) { const auto fullWidth = _inner.x() + appearedWidth + extents.right(); const auto size = QSize(fullWidth, _outer.height()); + q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip)); _strip.paint( q, - { _inner.x() + _skipx, _inner.y() + _skipy }, + { _skipx, _skipy }, { _size, 0 }, - { _inner.x(), _inner.y(), appearedWidth, _inner.height() }, + { 0, 0, appearedWidth, _inner.height() }, 1., false); _cachedRound.setBackgroundColor(st::defaultPopupMenu.menu.itemBg->c); _cachedRound.setShadowColor(st::shadowFg->c); + q.translate(QPoint(0, _collapsedTopSkip) - _inner.topLeft()); const auto radius = st::reactStripHeight / 2; _cachedRound.overlayExpandedBorder(q, size, _appearProgress, radius, 1.); q.setCompositionMode(QPainter::CompositionMode_Source); @@ -154,38 +164,114 @@ void Selector::paintAppearing(QPainter &p) { q.end(); p.drawImage( - QPoint(), + _outer.topLeft(), _paintBuffer, QRect(QPoint(), QSize(fullWidth, height()) * factor)); } void Selector::paintBackgroundToBuffer() { - if (_paintBuffer.size() != size() * style::DevicePixelRatio()) { - _paintBuffer = _cachedRound.PrepareImage(size()); + const auto factor = style::DevicePixelRatio(); + if (_paintBuffer.size() != _outerWithBubble.size() * factor) { + _paintBuffer = _cachedRound.PrepareImage(_outerWithBubble.size()); } _paintBuffer.fill(Qt::transparent); auto p = QPainter(&_paintBuffer); - _cachedRound.FillWithImage(p, _outer, _cachedRound.validateFrame(0, 1.)); + const auto radius = _inner.height() / 2.; + const auto frame = _cachedRound.validateFrame(0, 1., radius); + const auto outer = _outer.translated(0, -_collapsedTopSkip); + _cachedRound.FillWithImage(p, outer, frame); paintBubble(p, _inner.width()); } -void Selector::paintHorizontal(QPainter &p) { +void Selector::paintCollapsed(QPainter &p) { if (_paintBuffer.isNull()) { paintBackgroundToBuffer(); } - p.drawImage(0, 0, _paintBuffer); - - const auto extents = extentsForShadow(); + p.drawImage(_outer.topLeft(), _paintBuffer); _strip.paint( p, - { _inner.x() + _skipx, _inner.y() + _skipy }, + _inner.topLeft() + QPoint(_skipx, _skipy), { _size, 0 }, _inner, 1., false); } +void Selector::paintExpanding(QPainter &p, float64 progress) { + paintExpandingBg(p, progress); + paintStripWithoutExpand(p); + paintFadingExpandIcon(p, progress); +} + +void Selector::paintExpandingBg(QPainter &p, float64 progress) { + constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount; + const auto frame = int(base::SafeRound(progress * (kFramesCount - 1))); + const auto radiusStart = st::reactStripHeight / 2.; + const auto radiusEnd = st::roundRadiusSmall; + const auto radius = radiusStart + progress * (radiusEnd - radiusStart); + const auto extents = extentsForShadow(); + const auto expanding = anim::easeOutCirc(1., progress); + const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding); + const auto expandDown = anim::interpolate( + 0, + (height() - _outer.y() - _outer.height()), + expanding); + const auto outer = _outer.marginsAdded({ 0, expandUp, 0, expandDown }); + const auto pattern = _cachedRound.validateFrame(frame, 1., radius); + const auto fill = _cachedRound.FillWithImage(p, outer, pattern); + if (!fill.isEmpty()) { + p.fillRect(fill, st::defaultPopupMenu.menu.itemBg); + } +} + +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; + } + p.setOpacity(1. - progress); + const auto sub = anim::interpolate(0, _size / 3, progress); + const auto expandIconPosition = _inner.topLeft() + + QPoint(_inner.width() - _size - _skipx, _skipy); + const auto expandIconRect = QRect( + expandIconPosition, + QSize(_size, _size) + ).marginsRemoved({ sub, sub, sub, sub }); + p.drawImage(expandIconRect, _expandIconCache); +} + +void Selector::paintExpanded(QPainter &p) { + paintExpandedBg(p); + paintStripWithoutExpand(p); +} + +void Selector::paintExpandedBg(QPainter &p) { + if (!_expandedBgReady) { + _expandedBgReady = true; + auto q = QPainter(&_paintBuffer); + q.setCompositionMode(QPainter::CompositionMode_Source); + const auto pattern = _cachedRound.validateFrame( + kFramesCount - 1, + 1., + st::roundRadiusSmall); + const auto fill = _cachedRound.FillWithImage(q, rect(), pattern); + if (!fill.isEmpty()) { + q.fillRect(fill, st::defaultPopupMenu.menu.itemBg); + } + } + p.drawImage(0, 0, _paintBuffer); +} + void Selector::paintBubble(QPainter &p, int innerWidth) { const auto &bubble = st::reactStripBubble; const auto bubbleRight = std::min( @@ -194,7 +280,7 @@ void Selector::paintBubble(QPainter &p, int innerWidth) { bubble.paint( p, _inner.x() + innerWidth - bubbleRight - bubble.width(), - _inner.y() + _inner.height(), + _inner.y() + _inner.height() - _collapsedTopSkip, width()); } @@ -202,8 +288,12 @@ void Selector::paintEvent(QPaintEvent *e) { auto p = QPainter(this); if (_appearing) { paintAppearing(p); + } else if (!_expanded) { + paintCollapsed(p); + } else if (const auto progress = _expanding.value(1.); progress < 1.) { + paintExpanding(p, progress); } else { - paintHorizontal(p); + paintExpanded(p); } } @@ -252,6 +342,7 @@ void Selector::mouseReleaseEvent(QMouseEvent *e) { if (selected == Strip::AddedButton::Premium) { _premiumPromoChosen.fire({}); } else if (selected == Strip::AddedButton::Expand) { + expand(); } else { const auto id = std::get_if(&selected); if (id && !id->empty()) { @@ -260,6 +351,33 @@ void Selector::mouseReleaseEvent(QMouseEvent *e) { } } +void Selector::expand() { + const auto parent = parentWidget()->geometry(); + const auto additionalBottom = parent.height() - y() - height(); + const auto additional = _specialExpandTopSkip + additionalBottom; + if (additionalBottom < 0 || additional <= 0) { + return; + } + if (additionalBottom > 0) { + resize(width(), height() + additionalBottom); + raise(); + } + _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); + _paintBuffer = _cachedRound.PrepareImage(size()); + _expanded = true; + _expanding.start([=] { update(); }, 0., 1., st::slideDuration); +} + bool AdjustMenuGeometryForSelector( not_null menu, QPoint desiredPosition, @@ -317,6 +435,7 @@ bool AdjustMenuGeometryForSelector( extents.right(), 0 )); + selector->setSpecialExpandTopSkip(additionalPaddingBottom); return menu->prepareGeometryFor(desiredPosition); } 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 74d5bdaeb..b2addec94 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -43,6 +43,7 @@ public: [[nodiscard]] QMargins extentsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int desiredHeight() const; + void setSpecialExpandTopSkip(int skip); void initGeometry(int innerTop); [[nodiscard]] rpl::producer chosen() const { @@ -68,13 +69,21 @@ private: void mouseReleaseEvent(QMouseEvent *e) override; void paintAppearing(QPainter &p); - void paintHorizontal(QPainter &p); + void paintCollapsed(QPainter &p); + void paintExpanding(QPainter &p, float64 progress); + void paintExpandingBg(QPainter &p, float64 progress); + void paintStripWithoutExpand(QPainter &p); + void paintFadingExpandIcon(QPainter &p, float64 progress); + void paintExpanded(QPainter &p); + void paintExpandedBg(QPainter &p); void paintBubble(QPainter &p, int innerWidth); void paintBackgroundToBuffer(); [[nodiscard]] int lookupSelectedIndex(QPoint position) const; void setSelected(int index); + void expand(); + const Data::PossibleItemReactions _reactions; Ui::RoundAreaWithShadow _cachedRound; Strip _strip; @@ -83,11 +92,16 @@ private: rpl::event_stream<> _premiumPromoChosen; QImage _paintBuffer; + Ui::Animations::Simple _expanding; float64 _appearProgress = 0.; float64 _appearOpacity = 0.; QRect _inner; QRect _outer; + QRect _outerWithBubble; + QImage _expandIconCache; QMargins _padding; + int _specialExpandTopSkip = 0; + int _collapsedTopSkip = 0; int _size = 0; int _recentRows = 0; int _columns = 0; @@ -97,6 +111,8 @@ private: int _pressed = -1; bool _appearing = false; bool _toggling = false; + bool _expanded = false; + bool _expandedBgReady = false; bool _small = false; bool _over = false; bool _low = false; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index eed9293c8..a1ec2c9ad 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit eed9293c80eefd7b6d5398b4a65bf7822a1364fc +Subproject commit a1ec2c9ade2b050bd307ecf669a2ae5019d70df4