diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 2df12fbec..4d3cbf5df 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -122,6 +122,8 @@ EmojiPan { removeSet: IconButton; boxLabel: FlatLabel; icons: ComposeIcons; + about: FlatLabel; + aboutPadding: margins; autocompleteBottomSkip: pixels; } @@ -617,6 +619,14 @@ defaultComposeIcons: ComposeIcons { { "chat/reactions_expand_panel", windowSubTextFg }, }; } +defaultEmojiPanAbout: FlatLabel(defaultFlatLabel) { + minWidth: 10px; + align: align(top); + textFg: windowSubTextFg; + style: TextStyle(defaultTextStyle) { + font: font(11px); + } +} defaultEmojiPan: EmojiPan { margin: margins(7px, 0px, 7px, 0px); padding: margins(7px, 0px, 4px, 7px); @@ -658,6 +668,8 @@ defaultEmojiPan: EmojiPan { removeSet: stickerPanRemoveSet; boxLabel: boxLabel; icons: defaultComposeIcons; + about: defaultEmojiPanAbout; + aboutPadding: margins(12px, 2px, 12px, 2px); autocompleteBottomSkip: 0px; } statusEmojiPan: EmojiPan(defaultEmojiPan) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 66784f774..4534818d3 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -635,7 +635,7 @@ void StickersListFooter::paint( if (context.expanding) { const auto both = clip.intersected( context.clip.marginsRemoved( - { context.radius, 0, context.radius, 0 })); + { 0/*context.radius*/, 0, context.radius, 0 })); if (both.isEmpty()) { return; } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index f7c684a38..2ead069d3 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2636,7 +2636,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { desiredPosition, reactItem, [=](ChosenReaction reaction) { reactionChosen(reaction); }, - TextWithEntities(), + (reactItem->reactionsAreTags() + ? TextWithEntities{ u"Organize your Saved Messages with tags. Learn More..."_q } + : TextWithEntities()), _controller->cachedReactionIconFactory().createMethod()) : AttachSelectorResult::Skipped; if (attached == AttachSelectorResult::Failed) { 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 172803a44..f0aa8a86f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/reactions/history_view_reactions_selector.h" +#include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" @@ -195,6 +196,7 @@ Selector::Selector( const style::EmojiPan &st, std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, + TextWithEntities about, IconFactory iconFactory, Fn close, bool child) @@ -207,6 +209,7 @@ Selector::Selector( ? ChatHelpers::EmojiListMode::FullReactions : ChatHelpers::EmojiListMode::RecentReactions), {}, + std::move(about), iconFactory, close, child) { @@ -227,6 +230,7 @@ Selector::Selector( { .customAllowed = true }, mode, std::move(recent), + {}, nullptr, close, child) { @@ -239,6 +243,7 @@ Selector::Selector( const Data::PossibleItemReactionsRef &reactions, ChatHelpers::EmojiListMode mode, std::vector recent, + TextWithEntities about, IconFactory iconFactory, Fn close, bool child) @@ -261,6 +266,12 @@ Selector::Selector( crl::guard(this, [=] { update(_inner); }), std::move(iconFactory)) : nullptr) +, _about(about.empty() + ? nullptr + : std::make_unique( + this, + rpl::single(about), + _st.about)) , _size(st::reactStripSize) , _skipx(countSkipLeft()) , _skipy((st::reactStripHeight - st::reactStripSize) / 2) { @@ -269,6 +280,8 @@ Selector::Selector( _useTransparency = child || Ui::Platform::TranslucentWindowsSupported(); } +Selector::~Selector() = default; + bool Selector::useTransparency() const { return _useTransparency; } @@ -332,6 +345,27 @@ int Selector::extendTopForCategories() const { return _reactions.customAllowed ? _st.footer : 0; } +int Selector::extendTopForCategoriesAndAbout(int width) const { + if (_about) { + const auto padding = _st.aboutPadding; + const auto available = width - padding.left() - padding.right(); + const auto countAboutHeight = [&](int width) { + _about->resizeToWidth(width); + return _about->height(); + }; + const auto desired = Ui::FindNiceTooltipWidth( + std::min(available, _st.about.minWidth * 2), + available, + countAboutHeight); + + _about->resizeToWidth(desired); + _aboutExtend = padding.top() + _about->height() + padding.bottom(); + } else { + _aboutExtend = 0; + } + return std::max(extendTopForCategories(), _aboutExtend); +} + int Selector::minimalHeight() const { return _skipy + std::min(_recentRows * _size, st::emojiPanMinHeight) @@ -351,19 +385,33 @@ void Selector::initGeometry(int innerTop) { const auto width = _useTransparency ? (innerWidth + margins.left() + margins.right()) : parent.width(); - const auto height = innerHeight + margins.top() + margins.bottom(); - const auto left = style::RightToLeft() ? 0 : (parent.width() - width); + const auto forAbout = width - margins.left() - margins.right(); _collapsedTopSkip = _useTransparency - ? (extendTopForCategories() + _specialExpandTopSkip) + ? (extendTopForCategoriesAndAbout(forAbout) + _specialExpandTopSkip) : 0; + _topAddOnExpand = extendTopForCategories() + - _aboutExtend + + _specialExpandTopSkip; + const auto height = margins.top() + + _aboutExtend + + innerHeight + + margins.bottom(); + const auto left = style::RightToLeft() ? 0 : (parent.width() - width); const auto top = innerTop - margins.top() - _collapsedTopSkip; const auto add = _st.icons.stripBubble.height() - margins.bottom(); - _outer = QRect(0, _collapsedTopSkip, width, height); + _outer = QRect(0, _collapsedTopSkip - _aboutExtend, width, height); _outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add }); setGeometry(_outerWithBubble.marginsAdded( - { 0, _collapsedTopSkip, 0, 0 } + { 0, _outer.y(), 0, 0} ).translated(left, top)); - _inner = _outer.marginsRemoved(margins); + _inner = _outer.marginsRemoved( + margins + QMargins{ 0, _aboutExtend, 0, 0 }); + if (_about) { + _about->move( + _inner.x() + (_inner.width() - _about->width()) / 2, + _outer.y() + margins.top() + _st.aboutPadding.top()); + _aboutCache = Ui::GrabWidgetToImage(_about.get()); + } if (!_strip) { expand(); @@ -390,6 +438,14 @@ void Selector::updateShowState( && !appearing && !_paintBuffer.isNull()) { paintBackgroundToBuffer(); + if (_about && _about->isHidden()) { + _about->show(); + } + } else if (_useTransparency + && !_appearing + && appearing + && _about) { + _about->hide(); } _appearing = appearing; _toggling = toggling; @@ -416,7 +472,6 @@ void Selector::paintAppearing(QPainter &p) { Expects(_strip != nullptr); p.setOpacity(_appearOpacity); - const auto factor = style::DevicePixelRatio(); if (_paintBuffer.size() != _outerWithBubble.size() * factor) { _paintBuffer = _cachedRound.PrepareImage(_outerWithBubble.size()); @@ -428,7 +483,7 @@ void Selector::paintAppearing(QPainter &p) { const auto fullWidth = _inner.x() + appearedWidth + margins.right(); const auto size = QSize(fullWidth, _outer.height()); - q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip)); + q.translate(_inner.topLeft() - QPoint(0, _outer.y())); _strip->paint( q, { _skipx, _skipy }, @@ -439,7 +494,7 @@ void Selector::paintAppearing(QPainter &p) { _cachedRound.setBackgroundColor(_st.bg->c); _cachedRound.setShadowColor(st::shadowFg->c); - q.translate(QPoint(0, _collapsedTopSkip) - _inner.topLeft()); + q.translate(QPoint(0, _outer.y()) - _inner.topLeft()); const auto radius = st::reactStripHeight / 2; _cachedRound.overlayExpandedBorder( q, @@ -460,6 +515,15 @@ void Selector::paintAppearing(QPainter &p) { _outer.topLeft(), _paintBuffer, QRect(QPoint(), QSize(fullWidth, height()) * factor)); + + const auto aboutRight = _inner.x() + appearedWidth; + if (_about && _about->isHidden() && aboutRight > _about->x()) { + const auto aboutWidth = aboutRight - _about->x(); + p.drawImage( + _about->geometry().topLeft(), + _aboutCache, + QRect(QPoint(), QSize(aboutWidth, _about->height()) * factor)); + } } void Selector::paintBackgroundToBuffer() { @@ -478,8 +542,11 @@ void Selector::paintBackgroundToBuffer() { auto p = QPainter(&_paintBuffer); 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); + const auto outer = _outer.translated(0, -_outer.y()); + const auto fill = _cachedRound.FillWithImage(p, outer, frame); + if (!fill.isEmpty()) { + p.fillRect(fill, _st.bg); + } paintBubble(p, _inner.width()); } @@ -507,6 +574,13 @@ void Selector::paintExpanding(Painter &p, float64 progress) { const auto rects = updateExpandingRects(progress); paintExpandingBg(p, rects); progress /= kFullDuration; + if (_about && !_aboutCache.isNull()) { + p.setClipping(false); + p.setOpacity((1. - progress) * (1. - progress)); + const auto y = _about->y() - _outer.y() + rects.outer.y(); + p.drawImage(_about->x(), y, _aboutCache); + p.setOpacity(1.); + } if (_footer) { _footer->paintExpanding( p, @@ -537,25 +611,36 @@ Selector::ExpandingRects Selector::updateExpandingRects(float64 progress) { : radiusStart; const auto margins = marginsForShadow(); const auto expanding = anim::easeOutCirc(1., progress); - const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding); + const auto expandUp = anim::interpolate(0, _topAddOnExpand, expanding); const auto expandDown = anim::interpolate( 0, (height() - _outer.y() - _outer.height()), expanding); const auto outer = _outer.marginsAdded({ 0, expandUp, 0, expandDown }); - const auto categories = anim::interpolate( - 0, - extendTopForCategories(), - expanding); - const auto inner = outer.marginsRemoved(margins); - _shadowTop = inner.y() + categories; + const auto inner = outer.marginsRemoved(margins + + QMargins{ + 0, + anim::interpolate(_aboutExtend, 0, expanding), + 0, + 0 }); + const auto list = outer.marginsRemoved(margins + + QMargins{ + 0, + anim::interpolate( + _aboutExtend, + extendTopForCategories(), + expanding), + 0, + 0 }); + _shadowTop = list.y(); + const auto categories = list.y() - inner.y(); _shadowSkip = (_useTransparency && 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 }), + .list = list, .radius = radius, .expanding = expanding, .finalBottom = height() - margins.bottom(), @@ -651,7 +736,7 @@ void Selector::paintBubble(QPainter &p, int innerWidth) { bubble.paint( p, _inner.x() + innerWidth - bubbleRight - bubble.width(), - _inner.y() + _inner.height() - _collapsedTopSkip, + _inner.y() + _inner.height() - _outer.y(), width()); } @@ -804,6 +889,9 @@ void Selector::expand() { base::call_delayed(kExpandDelay, this, [this] { const auto full = kExpandDuration + kScaleDuration; + if (_about) { + _about->hide(); + } _expanded = true; _paintBuffer = _cachedRound.PrepareImage(size()); _expanding.start([=] { update(); }, 0., full, full); @@ -932,12 +1020,12 @@ bool AdjustMenuGeometryForSelector( const auto maxWidth = menu->st().menu.widthMax + added; const auto width = selector->countWidth(desiredWidth, maxWidth); const auto margins = selector->marginsForShadow(); - const auto categoriesTop = selector->useTransparency() - ? selector->extendTopForCategories() + const auto categoriesAboutTop = selector->useTransparency() + ? selector->extendTopForCategoriesAndAbout(width) : 0; menu->setForceWidth(width - added); const auto height = menu->height(); - const auto fullTop = margins.top() + categoriesTop + extend.top(); + const auto fullTop = margins.top() + categoriesAboutTop + extend.top(); const auto minimalHeight = margins.top() + selector->minimalHeight() + margins.bottom(); @@ -1063,7 +1151,7 @@ AttachSelectorResult AttachSelectorToMenu( QPoint desiredPosition, not_null item, Fn chosen, - TextWithEntities description, + TextWithEntities about, IconFactory iconFactory) { const auto result = AttachSelectorToMenu( menu, @@ -1071,6 +1159,7 @@ AttachSelectorResult AttachSelectorToMenu( st::reactPanelEmojiPan, controller->uiShow(), Data::LookupPossibleReactions(item), + std::move(about), std::move(iconFactory)); if (!result) { return result.error(); @@ -1103,6 +1192,7 @@ auto AttachSelectorToMenu( const style::EmojiPan &st, std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, + TextWithEntities about, IconFactory iconFactory) -> base::expected, AttachSelectorResult> { if (reactions.recent.empty()) { @@ -1114,6 +1204,7 @@ auto AttachSelectorToMenu( st, std::move(show), std::move(reactions), + std::move(about), std::move(iconFactory), [=](bool fast) { menu->hideMenu(fast); }, false); // child 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 b81ea4bca..a9fb7c3e1 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -36,6 +36,7 @@ namespace Ui { class PopupMenu; class ScrollArea; class PlainShadow; +class FlatLabel; } // namespace Ui namespace HistoryView::Reactions { @@ -80,6 +81,7 @@ public: const style::EmojiPan &st, std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, + TextWithEntities about, IconFactory iconFactory, Fn close, bool child = false); @@ -91,12 +93,14 @@ public: std::vector recent, Fn close, bool child = false); + ~Selector(); [[nodiscard]] bool useTransparency() const; int countWidth(int desiredWidth, int maxWidth); [[nodiscard]] QMargins marginsForShadow() const; [[nodiscard]] int extendTopForCategories() const; + [[nodiscard]] int extendTopForCategoriesAndAbout(int width) const; [[nodiscard]] int minimalHeight() const; [[nodiscard]] int countAppearedWidth(float64 progress) const; void setSpecialExpandTopSkip(int skip); @@ -137,6 +141,7 @@ private: const Data::PossibleItemReactionsRef &reactions, ChatHelpers::EmojiListMode mode, std::vector recent, + TextWithEntities about, IconFactory iconFactory, Fn close, bool child); @@ -179,6 +184,8 @@ private: Fn _jumpedToPremium; Ui::RoundAreaWithShadow _cachedRound; std::unique_ptr _strip; + std::unique_ptr _about; + mutable int _aboutExtend = 0; rpl::event_stream _chosen; rpl::event_stream<> _willExpand; @@ -200,9 +207,11 @@ private: QRect _outer; QRect _outerWithBubble; QImage _expandIconCache; + QImage _aboutCache; QMargins _padding; int _specialExpandTopSkip = 0; int _collapsedTopSkip = 0; + int _topAddOnExpand = 0; const int _size = 0; int _recentRows = 0; int _columns = 0; @@ -243,7 +252,7 @@ AttachSelectorResult AttachSelectorToMenu( QPoint desiredPosition, not_null item, Fn chosen, - TextWithEntities description, + TextWithEntities about, IconFactory iconFactory); [[nodiscard]] auto AttachSelectorToMenu( @@ -252,6 +261,7 @@ AttachSelectorResult AttachSelectorToMenu( const style::EmojiPan &st, std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, + TextWithEntities about, IconFactory iconFactory ) -> base::expected, AttachSelectorResult>; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 9e1be003c..420e0bee1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -675,6 +675,9 @@ void Reactions::Panel::create() { st::storiesReactionsPan, _controller->uiShow(), std::move(reactions), + TextWithEntities{ (mode == Mode::Message + ? u"Send reaction as a private message"_q + : QString()) }, _controller->cachedReactionIconFactory().createMethod(), [=](bool fast) { hide(mode); }); @@ -689,7 +692,8 @@ void Reactions::Panel::create() { const auto maxWidth = desiredWidth * 2; const auto width = _selector->countWidth(desiredWidth, maxWidth); const auto margins = _selector->marginsForShadow(); - const auto categoriesTop = _selector->extendTopForCategories(); + const auto categoriesTop = _selector->extendTopForCategoriesAndAbout( + width); const auto full = margins.left() + width + margins.right(); _shownValue = 0.; @@ -874,6 +878,7 @@ auto Reactions::attachToMenu( st::storiesReactionsPan, show, LookupPossibleReactions(&show->session()), + TextWithEntities(), _controller->cachedReactionIconFactory().createMethod()); if (!result) { return result.error();