From 8d2ebdbb991722a752b1b65f2fd80651b9ba4a0c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 18 Aug 2022 17:31:38 +0300 Subject: [PATCH] Animate reactions strip appearance. --- .../chat_helpers/chat_helpers.style | 2 + .../view/history_view_react_button.cpp | 395 ++---------------- .../history/view/history_view_react_button.h | 60 +-- .../view/history_view_react_selector.cpp | 135 +++++- .../view/history_view_react_selector.h | 31 +- .../media/history_view_media_unwrapped.cpp | 3 +- Telegram/lib_ui | 2 +- 7 files changed, 190 insertions(+), 438 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 2f830610c..70caa3f8c 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -314,3 +314,5 @@ stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }}; reactStripExtend: margins(21px, 49px, 39px, 0px); reactStripHeight: 40px; +reactStripSize: 32px; +reactStripSkip: 7px; diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 8645ad056..e4578c7b5 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -33,13 +33,7 @@ constexpr auto kToggleDuration = crl::time(120); constexpr auto kActivateDuration = crl::time(150); constexpr auto kExpandDuration = crl::time(300); constexpr auto kCollapseDuration = crl::time(250); -constexpr auto kBgCacheIndex = 0; -constexpr auto kShadowCacheIndex = 0; -constexpr auto kEmojiCacheIndex = 1; -constexpr auto kCacheColumsCount = 2; -constexpr auto kOverlayMaskCacheIndex = 0; -constexpr auto kOverlayShadowCacheIndex = 1; -constexpr auto kOverlayCacheColumsCount = 2; +constexpr auto kEmojiCacheIndex = 0; constexpr auto kButtonShowDelay = crl::time(300); constexpr auto kButtonExpandDelay = crl::time(25); constexpr auto kButtonHideDelay = crl::time(300); @@ -358,53 +352,19 @@ Manager::Manager( : _iconFactory(std::move(iconFactory)) , _outer(CountOuterSize()) , _inner(QRect({}, st::reactionCornerSize)) -, _overlayFull( - QRect(0, 0, _inner.width(), _inner.width()).marginsAdded( - st::reactionCornerShadow - ).size()) +, _cachedRound( + st::reactionCornerSize, + st::reactionCornerShadow, + _inner.width()) , _uniqueLimit(std::move(uniqueLimitValue)) , _buttonShowTimer([=] { showButtonDelayed(); }) , _buttonUpdate(std::move(buttonUpdate)) { - static_assert(!(kFramesCount % kDivider)); - _inner.translate(QRect({}, _outer).center() - _inner.center()); - const auto ratio = style::DevicePixelRatio(); - _cacheBg = QImage( - _outer.width() * kDivider * ratio, - _outer.height() * kFramesCount / kDivider * ratio, - QImage::Format_ARGB32_Premultiplied); - _cacheBg.setDevicePixelRatio(ratio); - _cacheBg.fill(Qt::transparent); - _cacheParts = QImage( - _outer.width() * kDivider * kCacheColumsCount * ratio, - _outer.height() * kFramesCount / kDivider * ratio, - QImage::Format_ARGB32_Premultiplied); - _cacheParts.setDevicePixelRatio(ratio); - _cacheParts.fill(Qt::transparent); - _overlayCacheParts = QImage( - _overlayFull.width() * kDivider * kOverlayCacheColumsCount * ratio, - _overlayFull.height() * kFramesCount / kDivider * ratio, - QImage::Format_ARGB32_Premultiplied); - _overlayCacheParts.setDevicePixelRatio(ratio); - _overlayMaskScaled = QImage( - _overlayFull * ratio, - QImage::Format_ARGB32_Premultiplied); - _overlayMaskScaled.setDevicePixelRatio(ratio); - _overlayShadowScaled = QImage( - _overlayFull * ratio, - QImage::Format_ARGB32_Premultiplied); - _overlayShadowScaled.setDevicePixelRatio(ratio); - _shadowBuffer = QImage( - _outer * ratio, - QImage::Format_ARGB32_Premultiplied); - _shadowBuffer.setDevicePixelRatio(ratio); - _expandedBuffer = QImage( - _outer.width() * ratio, - (_outer.height() + st::reactionCornerAddedHeightMax) * ratio, - QImage::Format_ARGB32_Premultiplied); - _expandedBuffer.setDevicePixelRatio(ratio); - + _emojiParts = _cachedRound.PrepareFramesCache(_outer); + _expandedBuffer = _cachedRound.PrepareImage(QSize( + _outer.width(), + _outer.height() + st::reactionCornerAddedHeightMax)); if (wheelEventsTarget) { stealWheelEvents(wheelEventsTarget); } @@ -736,7 +696,6 @@ void Manager::resolveMainReactionIcon() { void Manager::setMainReactionIcon() { _mainReactionLifetime.destroy(); - ranges::fill(_validBg, false); ranges::fill(_validEmoji, false); loadIcons(); const auto i = _loadCache.find(_mainReactionMedia->owner()); @@ -1013,17 +972,13 @@ void Manager::paintButton( const auto q = expanded ? &layeredPainter.emplace(&_expandedBuffer) : &p; const auto shadow = context.st->shadowFg()->c; const auto background = context.st->windowBg()->c; - setShadowColor(shadow); - setBackgroundColor(background); + _cachedRound.setShadowColor(shadow); + _cachedRound.setBackgroundColor(background); if (expanded) { q->fillRect(QRect(QPoint(), size), context.st->windowBg()); } else { - const auto source = validateFrame( - frameIndex, - scale, - background, - shadow); - p.drawImage(position, _cacheBg, source); + const auto frame = _cachedRound.validateFrame(frameIndex, scale); + p.drawImage(position, *frame.image, frame.rect); } const auto current = (button == _button.get()); @@ -1068,18 +1023,25 @@ void Manager::paintButton( ? QPoint(0, expanded - appearShift) : QPoint(0, appearShift); q->setOpacity(1. - opacity); - q->drawImage(appearPosition, _cacheParts, source); + q->drawImage(appearPosition, _emojiParts, source); q->setOpacity(1.); } } else { - p.drawImage(mainEmojiPosition, _cacheParts, source); + p.drawImage(mainEmojiPosition, _emojiParts, source); } if (current && !expanded) { clearAppearAnimations(); } if (expanded) { - overlayExpandedBorder(*q, size, expandRatio, scale, shadow); + const auto radiusMin = _inner.height() / 2.; + const auto radiusMax = _inner.width() / 2.; + _cachedRound.overlayExpandedBorder( + *q, + size, + expandRatio, + radiusMin + expandRatio * (radiusMax - radiusMin), + scale); layeredPainter.reset(); p.drawImage( geometry, @@ -1097,6 +1059,10 @@ void Manager::paintInnerGradients( not_null button, int scroll, float64 expandRatio) { + if (_gradientBackground != background) { + _gradientBackground = background; + _topGradient = _bottomGradient = QImage(); + } const auto endScroll = button->scrollMax() - scroll; const auto size = st::reactionGradientSize; const auto ensureGradient = [&](QImage &gradient, bool top) { @@ -1131,164 +1097,6 @@ void Manager::paintInnerGradients( p.setOpacity(1.); } -Manager::OverlayImage Manager::validateOverlayMask( - int frameIndex, - QSize innerSize, - float64 radius, - float64 scale) { - const auto ratio = style::DevicePixelRatio(); - const auto cached = (scale == 1.); - const auto full = cached - ? overlayCacheRect(frameIndex, kOverlayMaskCacheIndex) - : QRect(QPoint(), _overlayFull * ratio); - - const auto maskSize = QSize( - _overlayFull.width(), - _overlayFull.height() + innerSize.height() - innerSize.width()); - - const auto result = OverlayImage{ - .cache = cached ? &_overlayCacheParts : &_overlayMaskScaled, - .source = QRect(full.topLeft(), maskSize * ratio), - }; - if (cached && _validOverlayMask[frameIndex]) { - return result; - } - - auto p = QPainter(result.cache.get()); - const auto position = full.topLeft() / ratio; - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(QRect(position, maskSize), Qt::transparent); - - auto hq = PainterHighQualityEnabler(p); - const auto inner = QRect(position + _inner.topLeft(), innerSize); - p.setPen(Qt::NoPen); - p.setBrush(Qt::white); - if (scale != 1.) { - const auto center = inner.center(); - p.save(); - p.translate(center); - p.scale(scale, scale); - p.translate(-center); - } - p.drawRoundedRect(inner, radius, radius); - if (scale != 1.) { - p.restore(); - } - - if (cached) { - _validOverlayMask[frameIndex] = true; - } - return result; -} - -Manager::OverlayImage Manager::validateOverlayShadow( - int frameIndex, - QSize innerSize, - float64 radius, - float64 scale, - const QColor &shadow, - const OverlayImage &mask) { - const auto ratio = style::DevicePixelRatio(); - const auto cached = (scale == 1.); - const auto full = cached - ? overlayCacheRect(frameIndex, kOverlayShadowCacheIndex) - : QRect(QPoint(), _overlayFull * ratio); - - const auto maskSize = QSize( - _overlayFull.width(), - _overlayFull.height() + innerSize.height() - innerSize.width()); - - const auto result = OverlayImage{ - .cache = cached ? &_overlayCacheParts : &_overlayShadowScaled, - .source = QRect(full.topLeft(), maskSize * ratio), - }; - if (cached && _validOverlayShadow[frameIndex]) { - return result; - } - - const auto position = full.topLeft() / ratio; - - _overlayShadowScaled.fill(Qt::transparent); - const auto inner = QRect(_inner.topLeft(), innerSize); - const auto add = style::ConvertScale(2.5); - const auto shift = style::ConvertScale(0.5); - const auto extended = QRectF(inner).marginsAdded({ add, add, add, add }); - { - auto p = QPainter(&_overlayShadowScaled); - p.setCompositionMode(QPainter::CompositionMode_Source); - auto hq = PainterHighQualityEnabler(p); - p.setPen(Qt::NoPen); - p.setBrush(shadow); - if (scale != 1.) { - const auto center = inner.center(); - p.translate(center); - p.scale(scale, scale); - p.translate(-center); - } - p.drawRoundedRect(extended.translated(0, shift), radius, radius); - p.end(); - } - - _overlayShadowScaled = Images::Blur(std::move(_overlayShadowScaled)); - - auto q = Painter(result.cache); - if (result.cache != &_overlayShadowScaled) { - q.setCompositionMode(QPainter::CompositionMode_Source); - q.drawImage( - QRect(position, maskSize), - _overlayShadowScaled, - QRect(QPoint(), maskSize * ratio)); - } - q.setCompositionMode(QPainter::CompositionMode_DestinationOut); - q.drawImage(QRect(position, maskSize), *mask.cache, mask.source); - - if (cached) { - _validOverlayShadow[frameIndex] = true; - } - return result; -} - -void Manager::overlayExpandedBorder( - Painter &p, - QSize size, - float64 expandRatio, - float64 scale, - const QColor &shadow) { - const auto radiusMin = _inner.height() / 2.; - const auto radiusMax = _inner.width() / 2.; - const auto progress = expandRatio; - const auto frame = int(base::SafeRound(progress * (kFramesCount - 1))); - const auto radius = radiusMin - + (frame / float64(kFramesCount - 1)) * (radiusMax - radiusMin); - const auto innerSize = QSize(_inner.width(), int(std::ceil(radius * 2))); - - const auto overlayMask = validateOverlayMask( - frame, - innerSize, - radius, - scale); - const auto overlayShadow = validateOverlayShadow( - frame, - innerSize, - radius, - scale, - shadow, - overlayMask); - - p.setCompositionMode(QPainter::CompositionMode_DestinationIn); - paintLongImage( - p, - QRect(QPoint(), size), - *overlayMask.cache, - overlayMask.source); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - paintLongImage( - p, - QRect(QPoint(), size), - *overlayShadow.cache, - overlayShadow.source); -} - bool Manager::onlyMainEmojiVisible() const { if (_icons.empty()) { return true; @@ -1330,37 +1138,6 @@ void Manager::clearAppearAnimations() { } } -void Manager::paintLongImage( - QPainter &p, - QRect geometry, - const QImage &image, - QRect source) { - const auto factor = style::DevicePixelRatio(); - const auto sourceHeight = (source.height() / factor); - const auto part = (sourceHeight / 2) - 1; - const auto fill = geometry.height() - 2 * part; - const auto half = part * factor; - const auto top = source.height() - half; - p.drawImage( - geometry.topLeft(), - image, - QRect(source.x(), source.y(), source.width(), half)); - p.drawImage( - QRect( - geometry.topLeft() + QPoint(0, part), - QSize(source.width() / factor, fill)), - image, - QRect( - source.x(), - source.y() + half, - source.width(), - top - half)); - p.drawImage( - geometry.topLeft() + QPoint(0, part + fill), - image, - QRect(source.x(), source.y() + top, source.width(), half)); -} - void Manager::paintAllEmoji( Painter &p, not_null button, @@ -1493,76 +1270,16 @@ void Manager::clearStateForSelectFinished(ReactionIcons &icon) { } } -void Manager::setShadowColor(const QColor &shadow) { - if (_shadow == shadow) { - return; - } - _shadow = shadow; - ranges::fill(_validBg, false); - ranges::fill(_validShadow, false); - ranges::fill(_validOverlayShadow, false); -} - -QRect Manager::cacheRect(int frameIndex, int columnIndex) const { - const auto ratio = style::DevicePixelRatio(); - const auto origin = QPoint( - _outer.width() * (kDivider * columnIndex + (frameIndex % kDivider)), - _outer.height() * (frameIndex / kDivider)); - return QRect(ratio * origin, ratio * _outer); -} - -QRect Manager::overlayCacheRect(int frameIndex, int columnIndex) const { - const auto ratio = style::DevicePixelRatio(); - const auto size = _overlayFull; - const auto origin = QPoint( - size.width() * (kDivider * columnIndex + (frameIndex % kDivider)), - size.height() * (frameIndex / kDivider)); - return QRect(ratio * origin, ratio * size); -} - -QRect Manager::validateShadow( - int frameIndex, - float64 scale, - const QColor &shadow) { - const auto result = cacheRect(frameIndex, kShadowCacheIndex); - if (_validShadow[frameIndex]) { - return result; - } - - _shadowBuffer.fill(Qt::transparent); - auto p = QPainter(&_shadowBuffer); - auto hq = PainterHighQualityEnabler(p); - const auto center = _inner.center(); - const auto add = style::ConvertScale(2.5); - const auto shift = style::ConvertScale(0.5); - const auto big = QRectF(_inner).marginsAdded({ add, add, add, add }); - const auto radius = big.height() / 2.; - p.setPen(Qt::NoPen); - p.setBrush(shadow); - if (scale != 1.) { - p.translate(center); - p.scale(scale, scale); - p.translate(-center); - } - p.drawRoundedRect(big.translated(0, shift), radius, radius); - p.end(); - _shadowBuffer = Images::Blur(std::move(_shadowBuffer)); - - auto q = QPainter(&_cacheParts); - q.setCompositionMode(QPainter::CompositionMode_Source); - q.drawImage(result.topLeft() / style::DevicePixelRatio(), _shadowBuffer); - - _validShadow[frameIndex] = true; - return result; -} - QRect Manager::validateEmoji(int frameIndex, float64 scale) { - const auto result = cacheRect(frameIndex, kEmojiCacheIndex); + const auto result = _cachedRound.FrameCacheRect( + frameIndex, + kEmojiCacheIndex, + _outer); if (_validEmoji[frameIndex]) { return result; } - auto p = QPainter(&_cacheParts); + auto p = QPainter(&_emojiParts); const auto ratio = style::DevicePixelRatio(); const auto position = result.topLeft() / ratio; p.setCompositionMode(QPainter::CompositionMode_Source); @@ -1590,54 +1307,6 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) { return result; } -void Manager::setBackgroundColor(const QColor &background) { - if (_background == background) { - return; - } - _background = background; - _topGradient = QImage(); - _bottomGradient = QImage(); - ranges::fill(_validBg, false); -} - -QRect Manager::validateFrame( - int frameIndex, - float64 scale, - const QColor &background, - const QColor &shadow) { - const auto result = cacheRect(frameIndex, kBgCacheIndex); - if (_validBg[frameIndex]) { - return result; - } - - const auto shadowSource = validateShadow(frameIndex, scale, shadow); - const auto position = result.topLeft() / style::DevicePixelRatio(); - auto p = QPainter(&_cacheBg); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.drawImage(position, _cacheParts, shadowSource); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - auto hq = PainterHighQualityEnabler(p); - const auto inner = _inner.translated(position); - const auto radius = inner.height() / 2.; - p.setPen(Qt::NoPen); - p.setBrush(background); - if (scale != 1.) { - const auto center = inner.center(); - p.save(); - p.translate(center); - p.scale(scale, scale); - p.translate(-center); - } - p.drawRoundedRect(inner, radius, radius); - if (scale != 1.) { - p.restore(); - } - - _validBg[frameIndex] = true; - return result; -} - std::optional Manager::lookupEffectArea(FullMsgId itemId) const { const auto i = _activeEffectAreas.find(itemId); return (i != end(_activeEffectAreas)) diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index c2903b2b2..f12ce3768 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/effects/animations.h" +#include "ui/effects/round_area_with_shadow.h" #include "ui/widgets/scroll_area.h" #include "data/data_message_reaction_id.h" #include "ui/chat/chat_style.h" @@ -228,11 +229,7 @@ private: mutable bool selected = false; mutable bool selectAnimated = false; }; - struct OverlayImage { - not_null cache; - QRect source; - }; - static constexpr auto kFramesCount = 32; + static constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount; void applyListFilters(); void showButtonDelayed(); @@ -268,49 +265,12 @@ private: not_null button, int scroll, float64 expandRatio); - void overlayExpandedBorder( - Painter &p, - QSize size, - float64 expandRatio, - float64 scale, - const QColor &shadow); - void paintLongImage( - QPainter &p, - QRect geometry, - const QImage &image, - QRect source); void resolveMainReactionIcon(); void setMainReactionIcon(); void clearAppearAnimations(); [[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const; - [[nodiscard]] QRect overlayCacheRect( - int frameIndex, - int columnIndex) const; - QRect validateShadow( - int frameIndex, - float64 scale, - const QColor &shadow); QRect validateEmoji(int frameIndex, float64 scale); - QRect validateFrame( - int frameIndex, - float64 scale, - const QColor &background, - const QColor &shadow); - OverlayImage validateOverlayMask( - int frameIndex, - QSize innerSize, - float64 radius, - float64 scale); - OverlayImage validateOverlayShadow( - int frameIndex, - QSize innerSize, - float64 radius, - float64 scale, - const QColor &shadow, - const OverlayImage &mask); - void setBackgroundColor(const QColor &background); - void setShadowColor(const QColor &shadow); void setSelectedIcon(int index) const; void clearStateForHidden(ReactionIcons &icon); @@ -338,24 +298,14 @@ private: Data::ReactionsFilter _filter; QSize _outer; QRect _inner; - QSize _overlayFull; - QImage _cacheBg; - QImage _cacheParts; - QImage _overlayCacheParts; - QImage _overlayMaskScaled; - QImage _overlayShadowScaled; - QImage _shadowBuffer; + Ui::RoundAreaWithShadow _cachedRound; + QImage _emojiParts; QImage _expandedBuffer; + QColor _gradientBackground; QImage _topGradient; QImage _bottomGradient; - std::array _validBg = { { false } }; - std::array _validShadow = { { false } }; std::array _validEmoji = { { false } }; - std::array _validOverlayMask = { { false } }; - std::array _validOverlayShadow = { { false } }; - QColor _background; QColor _gradient; - QColor _shadow; std::shared_ptr _mainReactionMedia; std::shared_ptr _mainReactionIcon; diff --git a/Telegram/SourceFiles/history/view/history_view_react_selector.cpp b/Telegram/SourceFiles/history/view/history_view_react_selector.cpp index bd4acffe6..e1e4d17e9 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_selector.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_selector.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "mainwindow.h" #include "styles/style_chat_helpers.h" +#include "styles/style_chat.h" namespace HistoryView::Reactions { @@ -114,15 +115,34 @@ void Selector::hide(anim::type animated) { PopupSelector::PopupSelector( not_null parent, PossibleReactions reactions) -: RpWidget(parent) { +: RpWidget(parent) +, _reactions(std::move(reactions)) +, _cachedRound( + QSize(st::reactStripSkip * 2 + st::reactStripSize, st::reactStripHeight), + st::reactionCornerShadow, + st::reactStripHeight) +, _size(st::reactStripSize) +, _skipx(st::reactStripSkip) +, _skipy((st::reactStripHeight - st::reactStripSize) / 2) +, _skipBottom(st::reactStripHeight - st::reactStripSize - _skipy) { } int PopupSelector::countWidth(int desiredWidth, int maxWidth) { - return maxWidth; + const auto added = _reactions.customAllowed + || _reactions.morePremiumAvailable; + const auto possibleColumns = std::min( + (desiredWidth - 2 * _skipx + _size - 1) / _size, + (maxWidth - 2 * _skipx) / _size); + _columns = std::min( + possibleColumns, + int(_reactions.recent.size()) + (added ? 1 : 0)); + _small = (possibleColumns - _columns > 1); + _recentRows = (_reactions.recent.size() + _columns - 1) / _columns; + return 2 * _skipx + _columns * _size; } QMargins PopupSelector::extentsForShadow() const { - return st::defaultPopupMenu.shadow.extend; + return st::reactionCornerShadow; } int PopupSelector::extendTopForCategories() const { @@ -130,11 +150,80 @@ int PopupSelector::extendTopForCategories() const { } int PopupSelector::desiredHeight() const { - return st::emojiPanMaxHeight; + return _reactions.customAllowed + ? st::emojiPanMaxHeight + : (_skipy + _recentRows * _size + _skipBottom); +} + +void PopupSelector::initGeometry(int innerTop) { + const auto extents = extentsForShadow(); + const auto parent = parentWidget()->rect(); + const auto innerWidth = 2 * _skipx + _columns * _size; + const auto innerHeight = st::reactStripHeight; + 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(); + setGeometry(left, top, width, height); + _inner = rect().marginsRemoved(extents); +} + +void PopupSelector::updateShowState( + float64 progress, + float64 opacity, + bool appearing, + bool toggling) { + _appearing = appearing; + _toggling = toggling; + _appearProgress = progress; + _appearOpacity = opacity; + if (_appearing && isHidden()) { + show(); + } else if (_toggling && !isHidden()) { + hide(); + } + update(); +} + +void PopupSelector::paintAppearing(QPainter &p) { + p.setOpacity(_appearOpacity); + + const auto factor = style::DevicePixelRatio(); + if (_appearBuffer.size() != size() * factor) { + _appearBuffer = _cachedRound.PrepareImage(size()); + } + _appearBuffer.fill(st::defaultPopupMenu.menu.itemBg->c); + auto q = QPainter(&_appearBuffer); + const auto extents = extentsForShadow(); + const auto appearedWidth = anim::interpolate( + _skipx * 2 + _size, + _inner.width(), + _appearProgress); + const auto fullWidth = appearedWidth + extents.left() + extents.right(); + const auto size = QSize(fullWidth, height()); + _cachedRound.setBackgroundColor(st::defaultPopupMenu.menu.itemBg->c); + _cachedRound.setShadowColor(st::shadowFg->c); + const auto radius = st::reactStripHeight / 2; + _cachedRound.overlayExpandedBorder(q, size, _appearProgress, radius, 1.); + q.end(); + + p.drawImage( + QPoint(), + _appearBuffer, + QRect(QPoint(), size * style::DevicePixelRatio())); +} + +void PopupSelector::paintBg(QPainter &p) { + _cachedRound.FillWithImage(p, rect(), _cachedRound.validateFrame(0, 1.)); } void PopupSelector::paintEvent(QPaintEvent *e) { - QPainter(this).fillRect(e->rect(), QColor(0, 128, 0, 128)); + auto p = QPainter(this); + if (_appearing) { + paintAppearing(p); + } else { + paintBg(p); + } } [[nodiscard]] PossibleReactions LookupPossibleReactions( @@ -196,7 +285,7 @@ void PopupSelector::paintEvent(QPaintEvent *e) { return result; } -bool SetupSelectorInMenuGeometry( +bool AdjustMenuGeometryForSelector( not_null menu, QPoint desiredPosition, not_null selector) { @@ -267,18 +356,34 @@ AttachSelectorResult AttachSelectorToMenu( const auto selector = Ui::CreateChild( menu.get(), std::move(reactions)); - if (!SetupSelectorInMenuGeometry(menu, desiredPosition, selector)) { + if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { return AttachSelectorResult::Failed; } - const auto extents = selector->extentsForShadow(); - const auto categoriesTop = selector->extendTopForCategories(); - selector->setGeometry( - extents.left(), - menu->preparedPadding().top() - st::reactStripExtend.top(), - menu->width() - extents.left() - extents.right(), - st::reactStripHeight); - selector->lower(); + const auto selectorInnerTop = menu->preparedPadding().top() + - st::reactStripExtend.top(); + selector->initGeometry(selectorInnerTop); selector->show(); + + const auto correctTop = selector->y(); + menu->showStateValue( + ) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) { + const auto origin = menu->preparedOrigin(); + using Origin = Ui::PanelAnimation::Origin; + if (origin == Origin::BottomLeft || origin == Origin::BottomRight) { + const auto add = state.appearing + ? (menu->rect().marginsRemoved( + menu->preparedPadding() + ).height() - state.appearingHeight) + : 0; + selector->move(selector->x(), correctTop + add); + } + selector->updateShowState( + std::min(state.widthProgress, state.heightProgress), + state.opacity, + state.appearing, + state.toggling); + }, selector->lifetime()); + return AttachSelectorResult::Attached; } diff --git a/Telegram/SourceFiles/history/view/history_view_react_selector.h b/Telegram/SourceFiles/history/view/history_view_react_selector.h index 3a6af8416..b33abd95e 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_selector.h +++ b/Telegram/SourceFiles/history/view/history_view_react_selector.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unique_qptr.h" #include "ui/effects/animation_value.h" +#include "ui/effects/round_area_with_shadow.h" #include "ui/rp_widget.h" namespace Data { @@ -69,12 +70,38 @@ public: [[nodiscard]] QMargins extentsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int desiredHeight() const; + void initGeometry(int innerTop); -protected: - void paintEvent(QPaintEvent *e); + void updateShowState( + float64 progress, + float64 opacity, + bool appearing, + bool toggling); private: + static constexpr int kFramesCount = 32; + + void paintEvent(QPaintEvent *e); + + void paintAppearing(QPainter &p); + void paintBg(QPainter &p); + + PossibleReactions _reactions; + QImage _appearBuffer; + Ui::RoundAreaWithShadow _cachedRound; + float64 _appearProgress = 0.; + float64 _appearOpacity = 0.; + QRect _inner; + QMargins _padding; + int _size = 0; + int _recentRows = 0; int _columns = 0; + int _skipx = 0; + int _skipy = 0; + int _skipBottom = 0; + bool _appearing = false; + bool _toggling = false; + bool _small = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 27648d799..47d1f851b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -408,8 +408,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { auto pixTop = (minHeight() - _contentSize.height()) / 2; // Link of content can be nullptr (e.g. sticker without stickerpack). // So we have to process it to avoid overriding the previous result. - if (_content->link() - && QRect({ pixLeft, pixTop }, _contentSize).contains(point)) { + if (_content->link() && inner.contains(point)) { result.link = _content->link(); return result; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a76cdf7ed..eed9293c8 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a76cdf7edffb6e9cbef515a0e599c3c14a8a5b53 +Subproject commit eed9293c80eefd7b6d5398b4a65bf7822a1364fc