diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 0503f04ff..22dd45ab4 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -106,6 +106,99 @@ std::unique_ptr MakePathShiftGradient( st->paletteChanged()); } +bool DefaultElementDelegate::elementUnderCursor( + not_null view) { + return false; +} + +float64 DefaultElementDelegate::elementHighlightOpacity( + not_null item) const { + return 0.; +} + +bool DefaultElementDelegate::elementInSelectionMode() { + return false; +} + +bool DefaultElementDelegate::elementIntersectsRange( + not_null view, + int from, + int till) { + return true; +} + +void DefaultElementDelegate::elementStartStickerLoop( + not_null view) { +} + +void DefaultElementDelegate::elementShowPollResults( + not_null poll, + FullMsgId context) { +} + +void DefaultElementDelegate::elementOpenPhoto( + not_null photo, + FullMsgId context) { +} + +void DefaultElementDelegate::elementOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { +} + +void DefaultElementDelegate::elementCancelUpload(const FullMsgId &context) { +} + +void DefaultElementDelegate::elementShowTooltip( + const TextWithEntities &text, + Fn hiddenCallback) { +} + +bool DefaultElementDelegate::elementHideReply( + not_null view) { + return false; +} + +bool DefaultElementDelegate::elementShownUnread( + not_null view) { + return view->data()->unread(view->data()->history()); +} + +void DefaultElementDelegate::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void DefaultElementDelegate::elementHandleViaClick( + not_null bot) { +} + +bool DefaultElementDelegate::elementIsChatWide() { + return false; +} + +void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) { +} + +void DefaultElementDelegate::elementStartInteraction( + not_null view) { +} + +void DefaultElementDelegate::elementStartPremium( + not_null view, + Element *replacing) { +} + +void DefaultElementDelegate::elementCancelPremium( + not_null view) { +} + +QString DefaultElementDelegate::elementAuthorRank( + not_null view) { + return {}; +} + SimpleElementDelegate::SimpleElementDelegate( not_null controller, Fn update) @@ -118,106 +211,15 @@ SimpleElementDelegate::SimpleElementDelegate( SimpleElementDelegate::~SimpleElementDelegate() = default; -bool SimpleElementDelegate::elementUnderCursor( - not_null view) { - return false; -} - -float64 SimpleElementDelegate::elementHighlightOpacity( - not_null item) const { - return 0.; -} - -bool SimpleElementDelegate::elementInSelectionMode() { - return false; -} - -bool SimpleElementDelegate::elementIntersectsRange( - not_null view, - int from, - int till) { - return true; -} - -void SimpleElementDelegate::elementStartStickerLoop( - not_null view) { -} - -void SimpleElementDelegate::elementShowPollResults( - not_null poll, - FullMsgId context) { -} - -void SimpleElementDelegate::elementOpenPhoto( - not_null photo, - FullMsgId context) { -} - -void SimpleElementDelegate::elementOpenDocument( - not_null document, - FullMsgId context, - bool showInMediaView) { -} - -void SimpleElementDelegate::elementCancelUpload(const FullMsgId &context) { -} - -void SimpleElementDelegate::elementShowTooltip( - const TextWithEntities &text, - Fn hiddenCallback) { -} - bool SimpleElementDelegate::elementAnimationsPaused() { return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); } -bool SimpleElementDelegate::elementHideReply(not_null view) { - return false; -} - -bool SimpleElementDelegate::elementShownUnread( - not_null view) { - return view->data()->unread(view->data()->history()); -} - -void SimpleElementDelegate::elementSendBotCommand( - const QString &command, - const FullMsgId &context) { -} - -void SimpleElementDelegate::elementHandleViaClick(not_null bot) { -} - -bool SimpleElementDelegate::elementIsChatWide() { - return false; -} - auto SimpleElementDelegate::elementPathShiftGradient() -> not_null { return _pathGradient.get(); } -void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) { -} - -void SimpleElementDelegate::elementStartInteraction( - not_null view) { -} - -void SimpleElementDelegate::elementStartPremium( - not_null view, - Element *replacing) { -} - -void SimpleElementDelegate::elementCancelPremium( - not_null view) { -} - -QString SimpleElementDelegate::elementAuthorRank( - not_null view) { - return {}; -} - TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { @@ -611,7 +613,24 @@ bool Element::isHidden() const { return isHiddenByGroup(); } +void Element::overrideMedia(std::unique_ptr media) { + Expects(!history()->owner().groups().find(data())); + + _text = Ui::Text::String(st::msgMinWidth); + _textWidth = -1; + _textHeight = 0; + + _media = std::move(media); + if (!pendingResize()) { + history()->owner().requestViewResize(this); + } + _flags |= Flag::MediaOverriden; +} + void Element::refreshMedia(Element *replacing) { + if (_flags & Flag::MediaOverriden) { + return; + } _flags &= ~Flag::HiddenByGroup; const auto item = data(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3faaa355b..1824f7137 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -117,13 +117,8 @@ public: not_null st, Fn update); -class SimpleElementDelegate : public ElementDelegate { +class DefaultElementDelegate : public ElementDelegate { public: - SimpleElementDelegate( - not_null controller, - Fn update); - ~SimpleElementDelegate(); - bool elementUnderCursor(not_null view) override; [[nodiscard]] float64 elementHighlightOpacity( not_null item) const override; @@ -147,7 +142,6 @@ public: void elementShowTooltip( const TextWithEntities &text, Fn hiddenCallback) override; - bool elementAnimationsPaused() override; bool elementHideReply(not_null view) override; bool elementShownUnread(not_null view) override; void elementSendBotCommand( @@ -155,7 +149,6 @@ public: const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; - not_null elementPathShiftGradient() override; void elementReplyTo(const FullMsgId &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( @@ -164,6 +157,17 @@ public: void elementCancelPremium(not_null view) override; QString elementAuthorRank(not_null view) override; +}; + +class SimpleElementDelegate : public DefaultElementDelegate { +public: + SimpleElementDelegate( + not_null controller, + Fn update); + ~SimpleElementDelegate(); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; protected: [[nodiscard]] not_null controller() const { @@ -264,6 +268,7 @@ public: CustomEmojiRepainting = 0x0100, ScheduledUntilOnline = 0x0200, TopicRootReply = 0x0400, + MediaOverriden = 0x0800, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -480,6 +485,8 @@ public: Data::ReactionId, std::unique_ptr>; + void overrideMedia(std::unique_ptr media); + virtual ~Element(); static void Hovered(Element *view); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 75ed5584b..947454ddd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -509,27 +509,27 @@ void Controller::initLayout() { .nameBoundingRect = nameBoundingRect(right, false), .nameFontSize = nameFontSize, }; - if (!_locationAreas.empty()) { - rebuildLocationAreas(layout); + if (!_areas.empty()) { + rebuildActiveAreas(layout); } return layout; }); } -void Controller::rebuildLocationAreas(const Layout &layout) const { - Expects(_locations.size() == _locationAreas.size()); - +void Controller::rebuildActiveAreas(const Layout &layout) const { const auto origin = layout.content.topLeft(); const auto scale = layout.content.size(); - for (auto i = 0, count = int(_locations.size()); i != count; ++i) { - auto &area = _locationAreas[i]; - const auto &general = _locations[i].area.geometry; + for (auto &area : _areas) { + const auto &general = area.original; area.geometry = QRect( int(base::SafeRound(general.x() * scale.width())), int(base::SafeRound(general.y() * scale.height())), int(base::SafeRound(general.width() * scale.width())), int(base::SafeRound(general.height() * scale.height())) ).translated(origin); + if (const auto reaction = area.reaction.get()) { + reaction->setAreaGeometry(area.geometry); + } } } @@ -914,9 +914,16 @@ bool Controller::changeShown(Data::Story *story) { const auto &locations = story ? story->locations() : std::vector(); + const auto &suggestedReactions = story + ? story->suggestedReactions() + : std::vector(); if (_locations != locations) { _locations = locations; - _locationAreas.clear(); + _areas.clear(); + } + if (_suggestedReactions != suggestedReactions) { + _suggestedReactions = suggestedReactions; + _areas.clear(); } return true; @@ -1082,26 +1089,47 @@ void Controller::updatePlayback(const Player::TrackState &state) { } } -ClickHandlerPtr Controller::lookupLocationHandler(QPoint point) const { +ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { const auto &layout = _layout.current(); - if (_locations.empty() || !layout) { + if ((_locations.empty() && _suggestedReactions.empty()) || !layout) { return nullptr; - } else if (_locationAreas.empty()) { - _locationAreas = _locations | ranges::views::transform([]( - const Data::StoryLocation &location) { - return LocationArea{ + } else if (_areas.empty()) { + _areas.reserve(_locations.size() + _suggestedReactions.size()); + for (const auto &location : _locations) { + _areas.push_back({ + .original = location.area.geometry, .rotation = location.area.rotation, .handler = std::make_shared( location.point), - }; - }) | ranges::to_vector; - rebuildLocationAreas(*layout); + }); + } + for (const auto &suggestedReaction : _suggestedReactions) { + const auto id = suggestedReaction.reaction; + _areas.push_back({ + .original = suggestedReaction.area.geometry, + .rotation = suggestedReaction.area.rotation, + .handler = std::make_shared([=] { + _reactions->applyLike(id); + }), + .reaction = _reactions->makeSuggestedReactionWidget( + suggestedReaction), + }); + } + rebuildActiveAreas(*layout); } - for (const auto &area : _locationAreas) { + const auto circleContains = [&](QRect circle) { + const auto radius = std::min(circle.width(), circle.height()) / 2; + const auto delta = circle.center() - point; + return QPoint::dotProduct(delta, delta) < (radius * radius); + }; + for (const auto &area : _areas) { const auto center = area.geometry.center(); const auto angle = -area.rotation; - if (area.geometry.contains(Rotated(point, center, angle))) { + const auto contains = area.reaction + ? circleContains(area.geometry) + : area.geometry.contains(Rotated(point, center, angle)); + if (contains) { return area.handler; } } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 9dc6ab657..cd0750fc7 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -68,6 +68,7 @@ enum class SiblingType; struct ContentLayout; class CaptionFullView; enum class ReactionsMode; +class SuggestedReactionView; enum class HeaderLayout { Normal, @@ -135,7 +136,7 @@ public: void ready(); void updateVideoPlayback(const Player::TrackState &state); - [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; + [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta); @@ -197,10 +198,12 @@ private: return peerId != 0; } }; - struct LocationArea { + struct ActiveArea { + QRectF original; QRect geometry; float64 rotation = 0.; ClickHandlerPtr handler; + std::unique_ptr reaction; }; void initLayout(); @@ -215,7 +218,7 @@ private: void updateContentFaded(); void updatePlayingAllowed(); void setPlayingAllowed(bool allowed); - void rebuildLocationAreas(const Layout &layout) const; + void rebuildActiveAreas(const Layout &layout) const; void hideSiblings(); void showSiblings(not_null session); @@ -284,7 +287,8 @@ private: bool _viewed = false; std::vector _locations; - mutable std::vector _locationAreas; + std::vector _suggestedReactions; + mutable std::vector _areas; std::vector _cachedSourcesList; int _cachedSourceIndex = -1; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 8057829e3..408c7b54f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_reactions.h" #include "base/event_filter.h" +#include "base/unixtime.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" #include "data/data_changes.h" @@ -16,17 +17,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reactions.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "history/admin_log/history_admin_log_item.h" +#include "history/view/media/history_view_custom_emoji.h" +#include "history/view/media/history_view_media_unwrapped.h" #include "history/view/reactions/history_view_reactions_selector.h" +#include "history/view/history_view_element.h" +#include "history/history_item_reply_markup.h" +#include "history/history_item.h" +#include "history/history.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "ui/chat/chat_style.h" #include "ui/effects/emoji_fly_animation.h" +#include "ui/effects/path_shift_gradient.h" #include "ui/effects/reaction_fly_animation.h" +#include "ui/text/text_isolated_emoji.h" #include "ui/widgets/popup_menu.h" #include "ui/animated_icon.h" #include "ui/painter.h" #include "styles/style_chat_helpers.h" +#include "styles/style_chat.h" #include "styles/style_media_view.h" #include "styles/style_widgets.h" +#include "styles/style_window.h" namespace Media::Stories { namespace { @@ -34,6 +47,241 @@ namespace { constexpr auto kReactionScaleOutTarget = 0.7; constexpr auto kReactionScaleOutDuration = crl::time(1000); constexpr auto kMessageReactionScaleOutDuration = crl::time(400); +constexpr auto kSuggestedBubbleSize = 1.0; +constexpr auto kSuggestedTailBigSize = 0.264; +constexpr auto kSuggestedTailBigOffset = 0.464; +constexpr auto kSuggestedTailSmallSize = 0.110; +constexpr auto kSuggestedTailSmallOffset = 0.697; +constexpr auto kSuggestedTailBigRotation = -42.29; +constexpr auto kSuggestedTailSmallRotation = -40.87; +constexpr auto kSuggestedReactionSize = 0.7; + +class ReactionView final + : public Ui::RpWidget + , public SuggestedReactionView + , public HistoryView::DefaultElementDelegate { +public: + ReactionView( + QWidget *parent, + not_null session, + const Data::SuggestedReaction &reaction); + + void setAreaGeometry(QRect geometry) override; + +private: + using Element = HistoryView::Element; + not_null delegate(); + HistoryView::Context elementContext() override; + bool elementAnimationsPaused() override; + bool elementShownUnread(not_null view) override; + not_null elementPathShiftGradient() override; + + void paintEvent(QPaintEvent *e) override; + + void cacheBackground(); + + Data::SuggestedReaction _data; + std::unique_ptr _chatStyle; + std::unique_ptr _pathGradient; + AdminLog::OwnedItem _fake; + QImage _background; + QRectF _bubbleGeometry; + int _size = 0; + int _mediaLeft = 0; + int _mediaTop = 0; + int _mediaWidth = 0; + int _mediaHeight = 0; + float64 _bubble = 0; + float64 _bigOffset = 0; + float64 _bigSize = 0; + float64 _smallOffset = 0; + float64 _smallSize = 0; + +}; + +[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem( + not_null delegate, + not_null history) { + Expects(history->peer->isUser()); + + const auto flags = MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId; + const auto replyTo = FullReplyTo(); + const auto viaBotId = UserId(); + const auto groupedId = uint64(); + const auto item = history->makeMessage( + history->nextNonHistoryEntryId(), + flags, + replyTo, + viaBotId, + base::unixtime::now(), + peerToUser(history->peer->id), + QString(), + TextWithEntities(), + MTP_messageMediaEmpty(), + HistoryMessageMarkupData(), + groupedId); + return AdminLog::OwnedItem(delegate, item); +} + +ReactionView::ReactionView( + QWidget *parent, + not_null session, + const Data::SuggestedReaction &reaction) +: RpWidget(parent) +, _data(reaction) +, _chatStyle(std::make_unique()) +, _pathGradient( + std::make_unique( + st::shadowFg, + st::shadowFg, + [=] { update(); })) +, _fake( + GenerateFakeItem( + delegate(), + session->data().history(PeerData::kServiceNotificationsId))) { + style::PaletteChanged() | rpl::start_with_next([=] { + _background = QImage(); + }, lifetime()); + + const auto view = _fake.get(); + const auto item = view->data(); + + const auto entityData = [&] { + const auto &id = _data.reaction; + const auto reactions = &session->data().reactions(); + reactions->preloadAnimationsFor(id); + if (const auto customId = id.custom()) { + return Data::SerializeCustomEmojiId(customId); + } + const auto type = Data::Reactions::Type::All; + const auto &list = reactions->list(type); + const auto i = ranges::find(list, id, &Data::Reaction::id); + return (i != end(list)) + ? Data::SerializeCustomEmojiId(i->selectAnimation->id) + : QString(); + }(); + + const auto emoji = Ui::Text::OnlyCustomEmoji{ + { { { entityData } } } + }; + view->overrideMedia(std::make_unique( + view, + std::make_unique(view, emoji))); + view->initDimensions(); + + _mediaLeft = st::msgMargin.left(); + _mediaTop = st::msgMargin.top(); + _mediaWidth = _mediaHeight = view->resizeGetHeight(st::windowMinWidth) + - _mediaTop + - st::msgMargin.bottom(); + + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null element) { + if (element == view) { + update(); + } + }, lifetime()); + + setAttribute(Qt::WA_TransparentForMouseEvents); + show(); +} + +void ReactionView::setAreaGeometry(QRect geometry) { + _size = std::min(geometry.width(), geometry.height()); + const auto scaled = [&](float64 scale) { + return int(base::SafeRound(scale * _size)); + }; + _bubble = _size * kSuggestedBubbleSize; + _bigOffset = _bubble * kSuggestedTailBigOffset; + _bigSize = _bubble * kSuggestedTailBigSize; + _smallOffset = _bubble * kSuggestedTailSmallOffset; + _smallSize = _bubble * kSuggestedTailSmallSize; + const auto add = int(base::SafeRound(_smallOffset + _smallSize)) + - (_size / 2); + setGeometry(geometry.marginsAdded({ add, add, add, add })); +} + +not_null ReactionView::delegate() { + return static_cast(this); +} + +void ReactionView::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + if (!_size) { + return; + } else if (_background.size() != size() * style::DevicePixelRatio()) { + cacheBackground(); + } + p.drawImage(0, 0, _background); + + auto hq = PainterHighQualityEnabler(p); + p.translate(_bubbleGeometry.center()); + p.scale( + kSuggestedReactionSize * _bubbleGeometry.width() / _mediaWidth, + kSuggestedReactionSize * _bubbleGeometry.height() / _mediaHeight); + p.rotate(_data.area.rotation); + p.translate( + -(_mediaLeft + (_mediaWidth / 2)), + -(_mediaTop + (_mediaHeight / 2))); + + auto context = Ui::ChatPaintContext{ + .st = _chatStyle.get(), + .viewport = rect(), + .clip = rect(), + .now = crl::now(), + }; + _fake->draw(p, context); +} + +void ReactionView::cacheBackground() { + const auto ratio = style::DevicePixelRatio(); + _background = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + _background.setDevicePixelRatio(ratio); + _background.fill(Qt::transparent); + + auto p = QPainter(&_background); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(_data.dark ? QColor(0, 0, 0, 128) : QColor(255, 255, 255)); + p.setCompositionMode(QPainter::CompositionMode_Source); + + _bubbleGeometry = QRectF( + (width() - _bubble) / 2., + (height() - _bubble) / 2., + _bubble, + _bubble); + p.drawEllipse(_bubbleGeometry); + + const auto center = QPointF(width() / 2., height() / 2.); + p.translate(center); + + auto previous = 0.; + const auto rotate = [&](float64 initial) { + if (_data.flipped) { + initial = 180 - initial; + } + auto rotation = _data.area.rotation - initial; + while (rotation < 0) { + rotation += 360; + } + while (rotation >= 360) { + rotation -= 360; + } + const auto delta = rotation - previous; + previous = rotation; + p.rotate(delta); + }; + const auto paintTailPart = [&](float64 offset, float64 size) { + p.drawEllipse(QRectF(offset - size / 2., -size / 2., size, size)); + }; + rotate(kSuggestedTailBigRotation); + paintTailPart(_bigOffset, _bigSize); + rotate(kSuggestedTailSmallRotation); + paintTailPart(_smallOffset, _smallSize); +} [[nodiscard]] Data::ReactionId HeartReactionId() { return { QString() + QChar(10084) }; @@ -67,6 +315,24 @@ constexpr auto kMessageReactionScaleOutDuration = crl::time(400); return result; } +HistoryView::Context ReactionView::elementContext() { + return HistoryView::Context::ContactPreview; +} + +bool ReactionView::elementAnimationsPaused() { + return false; +} + +bool ReactionView::elementShownUnread( + not_null view) { + return false; +} + +auto ReactionView::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + } // namespace class Reactions::Panel final { @@ -358,6 +624,15 @@ auto Reactions::chosen() const -> rpl::producer { return _chosen.events(); } +auto Reactions::makeSuggestedReactionWidget( + const Data::SuggestedReaction &reaction) +-> std::unique_ptr { + return std::make_unique( + _controller->wrap(), + &_controller->uiShow()->session(), + reaction); +} + void Reactions::setReplyFieldState( rpl::producer focused, rpl::producer hasSendText) { @@ -458,9 +733,12 @@ void Reactions::outsidePressed() { void Reactions::toggleLiked() { const auto liked = !_liked.current().empty(); - const auto now = liked ? Data::ReactionId() : HeartReactionId(); - if (_liked.current() != now) { - animateAndProcess({ { .id = now }, ReactionsMode::Reaction }); + applyLike(liked ? Data::ReactionId() : HeartReactionId()); +} + +void Reactions::applyLike(Data::ReactionId id) { + if (_liked.current() != id) { + animateAndProcess({ { .id = id }, ReactionsMode::Reaction }); } } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index fbcf34b3c..7f7d22aea 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -15,6 +15,7 @@ class DocumentMedia; struct ReactionId; class Session; class Story; +struct SuggestedReaction; } // namespace Data namespace HistoryView::Reactions { @@ -40,6 +41,13 @@ enum class ReactionsMode { Reaction, }; +class SuggestedReactionView { +public: + virtual ~SuggestedReactionView() = default; + + virtual void setAreaGeometry(QRect geometry) = 0; +}; + class Reactions final { public: explicit Reactions(not_null controller); @@ -64,8 +72,13 @@ public: void hide(); void outsidePressed(); void toggleLiked(); + void applyLike(Data::ReactionId id); void ready(); + [[nodiscard]] auto makeSuggestedReactionWidget( + const Data::SuggestedReaction &reaction) + -> std::unique_ptr; + void setReplyFieldState( rpl::producer focused, rpl::producer hasSendText); diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 41390c3c4..0347a5e6e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -59,8 +59,8 @@ void View::updatePlayback(const Player::TrackState &state) { _controller->updateVideoPlayback(state); } -ClickHandlerPtr View::lookupLocationHandler(QPoint point) const { - return _controller->lookupLocationHandler(point); +ClickHandlerPtr View::lookupAreaHandler(QPoint point) const { + return _controller->lookupAreaHandler(point); } bool View::subjumpAvailable(int delta) const { diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index 730488253..081536299 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -81,7 +81,7 @@ public: void showFullCaption(); void updatePlayback(const Player::TrackState &state); - [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; + [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta) const; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 3a142408f..fb153ed80 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -5685,7 +5685,7 @@ void OverlayWidget::updateOver(QPoint pos) { lnk = _groupThumbs->getState(point); lnkhost = this; } else if (_stories) { - lnk = _stories->lookupLocationHandler(pos); + lnk = _stories->lookupAreaHandler(pos); lnkhost = this; }