diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 72a19a070..69be637ce 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -462,16 +462,16 @@ HistoryItem::HistoryItem( .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0), .date = data.vdate().v, }) { - if (data.vaction().type() != mtpc_messageActionPhoneCall) { - createServiceFromMtp(data); - } else { + data.vaction().match([&](const MTPDmessageActionPhoneCall &data) { createComponents(CreateConfig()); _media = std::make_unique( this, - Data::ComputeCallData( - data.vaction().c_messageActionPhoneCall())); + Data::ComputeCallData(data)); setTextValue({}); - } + }, [&](const auto &) { + createServiceFromMtp(data); + }); + setReactions(data.vreactions()); applyTTL(data); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 3357767db..ef12b365f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_item_helpers.h" #include "base/unixtime.h" +#include "boxes/premium_preview_box.h" #include "core/application.h" #include "core/core_settings.h" #include "core/click_handler_types.h" @@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_app_config.h" #include "main/main_session.h" #include "chat_helpers/stickers_emoji_pack.h" +#include "payments/payments_reaction_process.h" // TryAddingPaidReaction. #include "window/window_session_controller.h" #include "ui/effects/path_shift_gradient.h" #include "ui/effects/reaction_fly_animation.h" @@ -593,6 +595,10 @@ Element::Element( } } +bool Element::embedReactionsInBubble() const { + return false; +} + not_null Element::delegate() const { return _delegate; } @@ -1591,7 +1597,122 @@ bool Element::isSignedAuthorElided() const { return false; } +void Element::setupReactions(Element *replacing) { + refreshReactions(); + auto animations = replacing + ? replacing->takeReactionAnimations() + : base::flat_map< + Data::ReactionId, + std::unique_ptr>(); + if (!animations.empty()) { + const auto repainter = [=] { repaint(); }; + for (const auto &[id, animation] : animations) { + animation->setRepaintCallback(repainter); + } + if (_reactions) { + _reactions->continueAnimations(std::move(animations)); + } + } +} + +void Element::refreshReactions() { + using namespace Reactions; + auto reactionsData = InlineListDataFromMessage(this); + if (reactionsData.reactions.empty()) { + setReactions(nullptr); + return; + } + if (!_reactions) { + const auto handlerFactory = [=](ReactionId id) { + const auto weak = base::make_weak(this); + return std::make_shared([=]( + ClickContext context) { + const auto strong = weak.get(); + if (!strong) { + return; + } + const auto item = strong->data(); + const auto controller = ExtractController(context); + if (item->reactionsAreTags()) { + if (item->history()->session().premium()) { + const auto tag = Data::SearchTagToQuery(id); + HashtagClickHandler(tag).onClick(context); + } else if (controller) { + ShowPremiumPreviewBox( + controller, + PremiumFeature::TagsForMessages); + } + return; + } + if (id.paid()) { + Payments::TryAddingPaidReaction( + item, + weak.get(), + 1, + std::nullopt, + controller->uiShow()); + return; + } else { + const auto source = HistoryReactionSource::Existing; + item->toggleReaction(id, source); + } + if (const auto now = weak.get()) { + const auto chosen = now->data()->chosenReactions(); + if (id.paid() || ranges::contains(chosen, id)) { + now->animateReaction({ + .id = id, + }); + } + } + }); + }; + setReactions(std::make_unique( + &history()->owner().reactions(), + handlerFactory, + [=] { customEmojiRepaint(); }, + std::move(reactionsData))); + } else { + auto was = _reactions->computeTagsList(); + _reactions->update(std::move(reactionsData), width()); + auto now = _reactions->computeTagsList(); + if (!was.empty() || !now.empty()) { + auto &owner = history()->owner(); + owner.viewTagsChanged(this, std::move(was), std::move(now)); + } + } +} + +void Element::setReactions(std::unique_ptr list) { + auto was = _reactions + ? _reactions->computeTagsList() + : std::vector(); + _reactions = std::move(list); + auto now = _reactions + ? _reactions->computeTagsList() + : std::vector(); + if (!was.empty() || !now.empty()) { + auto &owner = history()->owner(); + owner.viewTagsChanged(this, std::move(was), std::move(now)); + } +} + +bool Element::updateReactions() { + const auto wasReactions = _reactions + ? _reactions->currentSize() + : QSize(); + refreshReactions(); + const auto nowReactions = _reactions + ? _reactions->currentSize() + : QSize(); + return (wasReactions != nowReactions); +} + void Element::itemDataChanged() { + if (updateReactions()) { + history()->owner().requestViewResize(this); + } else { + repaint(); + } } void Element::itemTextUpdated() { @@ -1615,6 +1736,9 @@ void Element::blockquoteExpandChanged() { void Element::unloadHeavyPart() { history()->owner().unregisterHeavyViewPart(this); + if (_reactions) { + _reactions->unloadCustomEmoji(); + } if (_media) { _media->unloadHeavyPart(); } @@ -1915,9 +2039,6 @@ void Element::clickHandlerPressedChanged( } } -void Element::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { -} - void Element::animateUnreadReactions() { const auto &recent = data()->recentReactions(); for (const auto &[id, list] : recent) { @@ -1931,6 +2052,9 @@ auto Element::takeReactionAnimations() -> base::flat_map< Data::ReactionId, std::unique_ptr> { + if (_reactions) { + return _reactions->takeAnimations(); + } return {}; } @@ -1950,6 +2074,8 @@ QRect Element::effectIconGeometry() const { } Element::~Element() { + setReactions(nullptr); + // Delete media while owner still exists. clearSpecialOnlyEmoji(); base::take(_media); @@ -2058,4 +2184,12 @@ int FindViewY(not_null view, uint16 symbol, int yfrom) { return origin.y() + (yfrom + ytill) / 2; } +Window::SessionController *ExtractController(const ClickContext &context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + return controller; + } + return nullptr; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3f688d9d3..abb1b3ada 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -338,6 +338,8 @@ public: Element *replacing, Flag serviceFlag); + [[nodiscard]] virtual bool embedReactionsInBubble() const; + [[nodiscard]] not_null delegate() const; [[nodiscard]] not_null data() const; [[nodiscard]] not_null history() const; @@ -561,9 +563,9 @@ public: [[nodiscard]] bool markSponsoredViewed(int shownFromTop) const; - virtual void animateReaction(Ui::ReactionFlyAnimationArgs &&args); + virtual void animateReaction(Ui::ReactionFlyAnimationArgs &&args) = 0; void animateUnreadReactions(); - [[nodiscard]] virtual auto takeReactionAnimations() + [[nodiscard]] auto takeReactionAnimations() -> base::flat_map< Data::ReactionId, std::unique_ptr>; @@ -617,6 +619,12 @@ protected: void clearSpecialOnlyEmoji(); void checkSpecialOnlyEmoji(); + void setupReactions(Element *replacing); + void refreshReactions(); + bool updateReactions(); + + std::unique_ptr _reactions; + private: // This should be called only from previousInBlocksChanged() // to add required bits to the Composer mask @@ -641,6 +649,7 @@ private: void setTextWithLinks( const TextWithEntities &text, const std::vector &links = {}); + void setReactions(std::unique_ptr list); struct TextWithLinks { TextWithEntities text; @@ -673,4 +682,7 @@ private: uint16 symbol, int yfrom = 0); +[[nodiscard]] Window::SessionController *ExtractController( + const ClickContext &context); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 736f60ed9..c1b85b8fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -38,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "main/main_session.h" -#include "payments/payments_reaction_process.h" // TryAddingPaidReaction. #include "ui/text/text_options.h" #include "ui/painter.h" #include "window/themes/window_theme.h" // IsNightMode. @@ -54,15 +53,6 @@ namespace { constexpr auto kPlayStatusLimit = 2; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; -[[nodiscard]] Window::SessionController *ExtractController( - const ClickContext &context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - return controller; - } - return nullptr; -} - class KeyboardStyle : public ReplyKeyboard::Style { public: KeyboardStyle(const style::BotKeyboardButton &st); @@ -423,22 +413,8 @@ Message::Message( } initLogEntryOriginal(); initPsa(); - refreshReactions(); - auto animations = replacing - ? replacing->takeReactionAnimations() - : base::flat_map< - Data::ReactionId, - std::unique_ptr>(); + setupReactions(replacing); auto animation = replacing ? replacing->takeEffectAnimation() : nullptr; - if (!animations.empty()) { - const auto repainter = [=] { repaint(); }; - for (const auto &[id, animation] : animations) { - animation->setRepaintCallback(repainter); - } - if (_reactions) { - _reactions->continueAnimations(std::move(animations)); - } - } if (animation) { _bottomInfo.continueEffectAnimation(std::move(animation)); } @@ -461,21 +437,6 @@ Message::~Message() { _fromNameStatus = nullptr; checkHeavyPart(); } - setReactions(nullptr); -} - -void Message::setReactions(std::unique_ptr list) { - auto was = _reactions - ? _reactions->computeTagsList() - : std::vector(); - _reactions = std::move(list); - auto now = _reactions - ? _reactions->computeTagsList() - : std::vector(); - if (!was.empty() || !now.empty()) { - auto &owner = history()->owner(); - owner.viewTagsChanged(this, std::move(was), std::move(now)); - } } void Message::refreshRightBadge() { @@ -694,16 +655,6 @@ void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) { } } -auto Message::takeReactionAnimations() --> base::flat_map< - Data::ReactionId, - std::unique_ptr> { - if (_reactions) { - return _reactions->takeAnimations(); - } - return {}; -} - auto Message::takeEffectAnimation() -> std::unique_ptr { return _bottomInfo.takeEffectAnimation(); @@ -2425,9 +2376,6 @@ bool Message::hasHeavyPart() const { void Message::unloadHeavyPart() { Element::unloadHeavyPart(); - if (_reactions) { - _reactions->unloadCustomEmoji(); - } _comments = nullptr; if (_fromNameStatus) { _fromNameStatus->custom = nullptr; @@ -3446,73 +3394,6 @@ bool Message::embedReactionsInBubble() const { return needInfoDisplay(); } -void Message::refreshReactions() { - using namespace Reactions; - auto reactionsData = InlineListDataFromMessage(this); - if (reactionsData.reactions.empty()) { - setReactions(nullptr); - return; - } - if (!_reactions) { - const auto handlerFactory = [=](ReactionId id) { - const auto weak = base::make_weak(this); - return std::make_shared([=]( - ClickContext context) { - const auto strong = weak.get(); - if (!strong) { - return; - } - const auto item = strong->data(); - const auto controller = ExtractController(context); - if (item->reactionsAreTags()) { - if (item->history()->session().premium()) { - const auto tag = Data::SearchTagToQuery(id); - HashtagClickHandler(tag).onClick(context); - } else if (controller) { - ShowPremiumPreviewBox( - controller, - PremiumFeature::TagsForMessages); - } - return; - } - if (id.paid()) { - Payments::TryAddingPaidReaction( - item, - weak.get(), - 1, - std::nullopt, - controller->uiShow()); - return; - } else { - const auto source = HistoryReactionSource::Existing; - item->toggleReaction(id, source); - } - if (const auto now = weak.get()) { - const auto chosen = now->data()->chosenReactions(); - if (id.paid() || ranges::contains(chosen, id)) { - now->animateReaction({ - .id = id, - }); - } - } - }); - }; - setReactions(std::make_unique( - &history()->owner().reactions(), - handlerFactory, - [=] { customEmojiRepaint(); }, - std::move(reactionsData))); - } else { - auto was = _reactions->computeTagsList(); - _reactions->update(std::move(reactionsData), width()); - auto now = _reactions->computeTagsList(); - if (!was.empty() || !now.empty()) { - auto &owner = history()->owner(); - owner.viewTagsChanged(this, std::move(was), std::move(now)); - } - } -} - void Message::validateInlineKeyboard(HistoryMessageReplyMarkup *markup) { if (!markup || markup->inlineKeyboard @@ -3554,18 +3435,17 @@ void Message::validateFromNameText(PeerData *from) const { } } -void Message::itemDataChanged() { +bool Message::updateBottomInfo() { const auto wasInfo = _bottomInfo.currentSize(); - const auto wasReactions = _reactions - ? _reactions->currentSize() - : QSize(); - refreshReactions(); _bottomInfo.update(BottomInfoDataFromMessage(this), width()); - const auto nowInfo = _bottomInfo.currentSize(); - const auto nowReactions = _reactions - ? _reactions->currentSize() - : QSize(); - if (wasInfo != nowInfo || wasReactions != nowReactions) { + return (_bottomInfo.currentSize() != wasInfo); +} + +void Message::itemDataChanged() { + const auto infoChanged = updateBottomInfo(); + const auto reactionsChanged = updateReactions(); + + if (infoChanged || reactionsChanged) { history()->owner().requestViewResize(this); } else { repaint(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index dc35277df..2cda78939 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -78,7 +78,7 @@ public: [[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const; [[nodiscard]] HistoryMessageEdited *displayedEditBadge(); - [[nodiscard]] bool embedReactionsInBubble() const; + bool embedReactionsInBubble() const override; int marginTop() const override; int marginBottom() const override; @@ -159,10 +159,6 @@ public: const base::flat_set &changes) override; void animateReaction(Ui::ReactionFlyAnimationArgs &&args) override; - auto takeReactionAnimations() - -> base::flat_map< - Data::ReactionId, - std::unique_ptr> override; void animateEffect(Ui::ReactionFlyAnimationArgs &&args) override; auto takeEffectAnimation() @@ -180,6 +176,8 @@ private: struct FromNameStatus; struct RightAction; + bool updateBottomInfo(); + void initLogEntryOriginal(); void initPsa(); void fromNameUpdated(int width) const; @@ -300,15 +298,12 @@ private: [[nodiscard]] ClickHandlerPtr psaTooltipLink() const; void psaTooltipToggled(bool shown) const; - void setReactions(std::unique_ptr list); void refreshRightBadge(); - void refreshReactions(); void validateFromNameText(PeerData *from) const; mutable std::unique_ptr _rightAction; mutable ClickHandlerPtr _fastReplyLink; mutable std::unique_ptr _viewButton; - std::unique_ptr _reactions; std::unique_ptr _topicButton; mutable std::unique_ptr _comments; diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 0de40ca7a..225ace5ac 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_media.h" +#include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" #include "history/history.h" #include "history/history_item.h" @@ -18,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "info/profile/info_profile_cover.h" #include "ui/chat/chat_style.h" +#include "ui/effects/reaction_fly_animation.h" #include "ui/text/text_options.h" #include "ui/painter.h" #include "ui/power_saving.h" @@ -405,6 +407,7 @@ Service::Service( not_null data, Element *replacing) : Element(delegate, data, replacing, Flag::ServiceMessage) { + setupReactions(replacing); } QRect Service::innerGeometry() const { @@ -423,7 +426,27 @@ QRect Service::countGeometry() const { if (delegate()->elementIsChatWide()) { result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } - return result.marginsRemoved(st::msgServiceMargin); + auto margins = st::msgServiceMargin; + margins.setTop(marginTop()); + return result.marginsRemoved(margins); +} + +void Service::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { + const auto item = data(); + + auto g = countGeometry(); + if (g.width() < 1 || isHidden()) { + return; + } + const auto repainter = [=] { repaint(); }; + + if (_reactions) { + const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height(); + const auto reactionsLeft = 0; + g.setHeight(g.height() - reactionsHeight); + const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); + _reactions->animate(args.translated(-reactionsPosition), repainter); + } } QSize Service::performCountCurrentSize(int newWidth) { @@ -439,12 +462,12 @@ QSize Service::performCountCurrentSize(int newWidth) { } const auto media = this->media(); const auto mediaDisplayed = media && media->isDisplayed(); + auto contentWidth = newWidth; if (mediaDisplayed && media->hideServiceText()) { newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(newWidth) + st::msgServiceMargin.bottom(); } else if (!text().isEmpty()) { - auto contentWidth = newWidth; if (delegate()->elementIsChatWide()) { accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); } @@ -465,12 +488,24 @@ QSize Service::performCountCurrentSize(int newWidth) { } } + if (_reactions) { + newHeight += st::mediaInBubbleSkip + + _reactions->resizeGetHeight(contentWidth); + if (hasRightLayout()) { + _reactions->flipToRight(); + } + } + return { newWidth, newHeight }; } QSize Service::performCountOptimalSize() { validateText(); + if (_reactions) { + _reactions->initDimensions(); + } + if (const auto media = this->media()) { media->initDimensions(); if (media->hideServiceText()) { @@ -487,7 +522,12 @@ bool Service::isHidden() const { } int Service::marginTop() const { - return st::msgServiceMargin.top(); + auto result = st::msgServiceMargin.top(); + result += displayedDateHeight(); + if (const auto bar = Get()) { + result += bar->height(); + } + return result; } int Service::marginBottom() const { @@ -499,42 +539,32 @@ void Service::draw(Painter &p, const PaintContext &context) const { if (g.width() < 1) { return; } - const auto &margin = st::msgServiceMargin; const auto st = context.st; - auto height = this->height() - margin.top() - margin.bottom(); - auto dateh = 0; - auto unreadbarh = 0; auto clip = context.clip; - if (auto date = Get()) { - dateh = date->height(); - p.translate(0, dateh); - clip.translate(0, -dateh); - height -= dateh; - } if (const auto bar = Get()) { - unreadbarh = bar->height(); - if (clip.intersects(QRect(0, 0, width(), unreadbarh))) { + auto unreadbarh = bar->height(); + auto dateh = 0; + if (const auto date = Get()) { + dateh = date->height(); + } + if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) { + p.translate(0, dateh); bar->paint( p, context, 0, width(), delegate()->elementIsChatWide()); + p.translate(0, -dateh); } - p.translate(0, unreadbarh); - clip.translate(0, -unreadbarh); - height -= unreadbarh; } if (isHidden()) { - if (auto skiph = dateh + unreadbarh) { - p.translate(0, -skiph); - } return; } - paintHighlight(p, context, height); + paintHighlight(p, context, g.height()); p.setTextPalette(st->serviceTextPalette()); @@ -542,13 +572,26 @@ void Service::draw(Painter &p, const PaintContext &context) const { const auto mediaDisplayed = media && media->isDisplayed(); const auto onlyMedia = (mediaDisplayed && media->hideServiceText()); - if (!onlyMedia) { - if (mediaDisplayed) { - height -= margin.top() + media->height(); + if (_reactions) { + const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height(); + const auto reactionsLeft = 0; + g.setHeight(g.height() - reactionsHeight); + const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); + p.translate(reactionsPosition); + prepareCustomEmojiPaint(p, context, *_reactions); + _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition)); + if (context.reactionInfo) { + context.reactionInfo->position = reactionsPosition; } - const auto trect = QRect(g.left(), margin.top(), g.width(), height) + p.translate(-reactionsPosition); + } + + if (!onlyMedia) { + const auto mediaSkip = mediaDisplayed ? (st::msgServiceMargin.top() + media->height()) : 0; + const auto trect = QRect(g.left(), g.top(), g.width(), g.height() - mediaSkip) - st::msgServicePadding; + p.translate(0, g.top() - st::msgServiceMargin.top()); ServiceMessagePainter::PaintComplexBubble( p, context.st, @@ -556,6 +599,7 @@ void Service::draw(Painter &p, const PaintContext &context) const { g.width(), text(), trect); + p.translate(0, -g.top() + st::msgServiceMargin.top()); p.setBrush(Qt::NoBrush); p.setPen(st->msgServiceFg()); @@ -575,15 +619,12 @@ void Service::draw(Painter &p, const PaintContext &context) const { }); } if (mediaDisplayed) { - const auto left = margin.left() + (g.width() - media->width()) / 2; - const auto top = margin.top() + (onlyMedia ? 0 : (height + margin.top())); - p.translate(left, top); - media->draw(p, context.translated(-left, -top).withSelection({})); - p.translate(-left, -top); - } - - if (auto skiph = dateh + unreadbarh) { - p.translate(0, -skiph); + const auto left = g.left() + (g.width() - media->width()) / 2; + const auto top = g.top() + (onlyMedia ? 0 : (g.height() - media->height())); + const auto position = QPoint(left, top); + p.translate(position); + media->draw(p, context.translated(-position).withSelection({})); + p.translate(-position); } } @@ -596,12 +637,6 @@ PointState Service::pointState(QPoint point) const { return PointState::Outside; } - if (const auto dateh = displayedDateHeight()) { - g.setTop(g.top() + dateh); - } - if (const auto bar = Get()) { - g.setTop(g.top() + bar->height()); - } if (mediaDisplayed) { const auto centerPadding = (g.width() - media->width()) / 2; const auto r = g - QMargins(centerPadding, 0, centerPadding, 0); @@ -626,24 +661,26 @@ TextState Service::textState(QPoint point, StateRequest request) const { return result; } - if (const auto dateh = displayedDateHeight()) { - point.setY(point.y() - dateh); - g.setHeight(g.height() - dateh); - } - if (const auto bar = Get()) { - auto unreadbarh = bar->height(); - point.setY(point.y() - unreadbarh); - g.setHeight(g.height() - unreadbarh); + if (_reactions) { + const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height(); + const auto reactionsLeft = 0; + g.setHeight(g.height() - reactionsHeight); + const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); + if (_reactions->getState(point - reactionsPosition, &result)) { + //result.symbol += visibleMediaTextLen + visibleTextLen; + return result; + } } + if (onlyMedia) { - return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, st::msgServiceMargin.top()), request); + return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, g.top()), request); } else if (mediaDisplayed) { g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height())); } const auto mediaLeft = st::msgServiceMargin.left() + (media ? ((g.width() - media->width()) / 2) : 0); - const auto mediaTop = st::msgServiceMargin.top() + const auto mediaTop = g.top() + g.height() + st::msgServiceMargin.top(); const auto mediaPoint = point - QPoint(mediaLeft, mediaTop); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index 76bf73e02..57dabbecc 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -54,6 +54,8 @@ public: bool consumeHorizontalScroll(QPoint position, int delta) override; + void animateReaction(Ui::ReactionFlyAnimationArgs &&args) override; + private: [[nodiscard]] QRect countGeometry() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp index cda0204c6..0e12ae006 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -225,11 +225,10 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const { TextState ServiceBox::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); + point.setY(point.y() - st::msgServiceGiftBoxTopSkip); const auto content = contentRect(); const auto lookupSubtitleLink = [&] { - auto top = st::msgServiceGiftBoxTopSkip - + content.top() - + content.height(); + auto top = content.top() + content.height(); const auto &padding = st::msgServiceGiftBoxTitlePadding; top += padding.top(); if (!_title.isEmpty()) { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 41390373b..8a1885236 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -180,6 +180,21 @@ void InlineList::layoutButtons() { _buttons = std::move(buttons); } +InlineList::Dimension InlineList::countDimension(int width) const { + using Flag = InlineListData::Flag; + const auto inBubble = (_data.flags & Flag::InBubble); + const auto centered = (_data.flags & Flag::Centered); + const auto useWidth = centered + ? std::min(width, st::chatGiveawayWidth) + : width; + const auto left = inBubble + ? st::reactionInlineInBubbleLeft + : centered + ? ((width - useWidth) / 2) + : 0; + return { .left = left, .width = useWidth }; +} + InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) { auto result = Button{ .id = id, .paid = id.paid()}; if (const auto customId = id.custom()) { @@ -258,9 +273,7 @@ QSize InlineList::countOptimalSize() { if (_buttons.empty()) { return _skipBlock; } - const auto left = (_data.flags & InlineListData::Flag::InBubble) - ? st::reactionInlineInBubbleLeft - : 0; + const auto left = countDimension(width()).left; auto x = left; const auto between = st::reactionInlineBetween; const auto padding = st::reactionInlinePadding; @@ -308,23 +321,42 @@ QSize InlineList::countCurrentSize(int newWidth) { } using Flag = InlineListData::Flag; const auto between = st::reactionInlineBetween; - const auto inBubble = (_data.flags & Flag::InBubble); - const auto left = inBubble ? st::reactionInlineInBubbleLeft : 0; + const auto dimension = countDimension(newWidth); + const auto left = dimension.left; + const auto width = dimension.width; + const auto centered = (_data.flags & Flag::Centered); auto x = left; auto y = 0; - for (auto &button : _buttons) { + const auto recenter = [&](int beforeIndex) { + const auto added = centered ? (left + width + between - x) : 0; + if (added <= 0) { + return; + } + const auto shift = added / 2; + for (auto j = beforeIndex; j != 0;) { + auto &button = _buttons[--j]; + if (button.geometry.y() != y) { + break; + } + button.geometry.translate(shift, 0); + } + }; + for (auto i = 0, count = int(_buttons.size()); i != count; ++i) { + auto &button = _buttons[i]; const auto size = button.geometry.size(); - if (x > left && x + size.width() > newWidth) { + if (x > left && x + size.width() > left + width) { + recenter(i); x = left; y += size.height() + between; } button.geometry = QRect(QPoint(x, y), size); x += size.width() + between; } + recenter(_buttons.size()); const auto &last = _buttons.back().geometry; const auto height = y + last.height(); const auto right = last.x() + last.width() + _skipBlock.width(); - const auto add = (right > newWidth) ? _skipBlock.height() : 0; + const auto add = (right > width) ? _skipBlock.height() : 0; return { newWidth, height + add }; } @@ -667,10 +699,9 @@ void InlineList::paintSingleBg( bool InlineList::getState( QPoint point, not_null outResult) const { - const auto left = (_data.flags & InlineListData::Flag::InBubble) - ? st::reactionInlineInBubbleLeft - : 0; - if (!QRect(left, 0, width() - left, height()).contains(point)) { + const auto dimension = countDimension(width()); + const auto left = dimension.left; + if (!QRect(left, 0, dimension.width, height()).contains(point)) { return false; } for (const auto &button : _buttons) { @@ -792,9 +823,9 @@ void InlineList::continueAnimations(base::flat_map< } } -InlineListData InlineListDataFromMessage(not_null message) { +InlineListData InlineListDataFromMessage(not_null view) { using Flag = InlineListData::Flag; - const auto item = message->data(); + const auto item = view->data(); auto result = InlineListData(); result.reactions = item->reactionsWithLocal(); if (const auto user = item->history()->peer->asUser()) { @@ -838,9 +869,10 @@ InlineListData InlineListDataFromMessage(not_null message) { } } } - result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag()) - | (message->embedReactionsInBubble() ? Flag::InBubble : Flag()) - | (item->reactionsAreTags() ? Flag::Tags : Flag()); + result.flags = (view->hasOutLayout() ? Flag::OutLayout : Flag()) + | (view->embedReactionsInBubble() ? Flag::InBubble : Flag()) + | (item->reactionsAreTags() ? Flag::Tags : Flag()) + | (item->isService() ? Flag::Centered : Flag()); return result; } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h index 31982fd1c..89b22c980 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h @@ -26,7 +26,7 @@ class CustomEmoji; namespace HistoryView { using PaintContext = Ui::ChatPaintContext; -class Message; +class Element; struct TextState; struct UserpicInRow; } // namespace HistoryView @@ -42,6 +42,7 @@ struct InlineListData { OutLayout = 0x02, Flipped = 0x04, Tags = 0x08, + Centered = 0x10, }; friend inline constexpr bool is_flag_type(Flag) { return true; }; using Flags = base::flags; @@ -99,6 +100,10 @@ public: [[nodiscard]] static QImage PrepareTagBg(QColor tagBg, QColor dotBg); private: + struct Dimension { + int left = 0; + int width = 0; + }; struct Userpics { QImage image; std::vector list; @@ -131,6 +136,7 @@ private: void validateTagBg(const QColor &color) const; QSize countOptimalSize() override; + [[nodiscard]] Dimension countDimension(int width) const; const not_null<::Data::Reactions*> _owner; const Fn _handlerFactory; @@ -147,7 +153,7 @@ private: }; [[nodiscard]] InlineListData InlineListDataFromMessage( - not_null message); + not_null view); [[nodiscard]] ReactionId ReactionIdOfLink(const ClickHandlerPtr &link);