diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 7375ff2b2c..ac2ab7b30f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1227,6 +1227,9 @@ ClickHandlerPtr Element::rightActionLink() const { return ClickHandlerPtr(); } +void Element::applyRightActionLastPoint(QPoint p) const { +} + TimeId Element::displayedEditDate() const { return TimeId(0); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 966cbb87db..5a1af85fc1 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -404,6 +404,7 @@ public: int top, int outerWidth) const; [[nodiscard]] virtual ClickHandlerPtr rightActionLink() const; + [[nodiscard]] virtual void applyRightActionLastPoint(QPoint p) const; [[nodiscard]] virtual TimeId displayedEditDate() const; [[nodiscard]] virtual bool hasVisibleText() const; [[nodiscard]] virtual HistoryMessageReply *displayedReply() const; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 99ee3f9073..8821b248b5 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -292,6 +292,12 @@ struct Message::FromNameStatus { int skip = 0; }; +struct Message::RightAction { + std::unique_ptr ripple; + ClickHandlerPtr link; + QPoint lastPoint; +}; + LogEntryOriginal::LogEntryOriginal() = default; LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other) @@ -1555,6 +1561,8 @@ void Message::clickHandlerPressedChanged( Element::clickHandlerPressedChanged(handler, pressed); if (!handler) { return; + } else if (_rightAction && (handler == _rightAction->link)) { + toggleRightActionRipple(pressed); } else if (_comments && (handler == _comments->link)) { toggleCommentsButtonRipple(pressed); } else if (_topicButton && (handler == _topicButton->link)) { @@ -1580,6 +1588,25 @@ void Message::toggleCommentsButtonRipple(bool pressed) { } } +void Message::toggleRightActionRipple(bool pressed) { + Expects(_rightAction != nullptr); + const auto size = rightActionSize(); + Assert(size != std::nullopt); + + if (pressed) { + if (!_rightAction->ripple) { + // Create a ripple. + _rightAction->ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask(*size, size->width() / 2), + [=] { repaint(); }); + } + _rightAction->ripple->add(_rightAction->lastPoint); + } else if (_rightAction->ripple) { + _rightAction->ripple->lastStop(); + } +} + BottomRippleMask Message::bottomRippleMask(int buttonHeight) const { using namespace Ui; using namespace Images; @@ -1923,7 +1950,7 @@ TextState Message::textState( } } checkBottomInfoState(); - if (const auto size = rightActionSize()) { + if (const auto size = rightActionSize(); size && _rightAction) { const auto fastShareSkip = std::clamp( (g.height() - size->height()) / 2, 0, @@ -1938,6 +1965,8 @@ TextState Message::textState( ).contains(point)) { result.link = rightActionLink(); } + applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } else if (media && media->isDisplayed()) { result = media->textState(point - g.topLeft(), request); @@ -2685,8 +2714,8 @@ auto Message::verticalRepaintRange() const -> VerticalRepaintRange { } void Message::refreshDataIdHook() { - if (base::take(_rightActionLink)) { - _rightActionLink = rightActionLink(); + if (_rightAction && base::take(_rightAction->link)) { + _rightAction->link = rightActionLink(); } if (base::take(_fastReplyLink)) { _fastReplyLink = fastReplyLink(); @@ -2960,6 +2989,13 @@ std::optional Message::rightActionSize() const { : std::optional(); } +void Message::applyRightActionLastPoint(QPoint p) const { + if (!_rightAction) { + _rightAction = std::make_unique(); + } + _rightAction->lastPoint = std::move(p); +} + bool Message::displayFastShare() const { const auto item = message(); const auto peer = item->history()->peer; @@ -3003,8 +3039,27 @@ void Message::drawRightAction( int left, int top, int outerWidth) const { + if (!_rightAction) { + _rightAction = std::make_unique(); + } + const auto size = rightActionSize(); const auto st = context.st; + + if (_rightAction->ripple) { + const auto &stm = context.messageStyle(); + const auto colorOverride = &stm->msgWaveformInactive->c; + _rightAction->ripple->paint( + p, + left, + top, + size->width(), + colorOverride); + if (_rightAction->ripple->empty()) { + _rightAction->ripple.reset(); + } + } + p.setPen(Qt::NoPen); p.setBrush(st->msgServiceBg()); { @@ -3050,15 +3105,18 @@ void Message::drawRightAction( } ClickHandlerPtr Message::rightActionLink() const { - if (_rightActionLink) { - return _rightActionLink; + if (!_rightAction) { + _rightAction = std::make_unique(); + } + if (_rightAction->link) { + return _rightAction->link; } if (isPinnedContext()) { - _rightActionLink = goToMessageClickHandler(data()); - return _rightActionLink; + _rightAction->link = goToMessageClickHandler(data()); + return _rightAction->link; } else if (displayRightActionComments()) { - _rightActionLink = createGoToCommentsLink(); - return _rightActionLink; + _rightAction->link = createGoToCommentsLink(); + return _rightAction->link; } const auto sessionId = data()->history()->session().uniqueId(); const auto owner = &data()->history()->owner(); @@ -3106,7 +3164,7 @@ ClickHandlerPtr Message::rightActionLink() const { } }; }; - _rightActionLink = std::make_shared([=]( + _rightAction->link = std::make_shared([=]( ClickContext context) { const auto controller = ExtractController(context).value_or(nullptr); if (!controller) { @@ -3129,7 +3187,7 @@ ClickHandlerPtr Message::rightActionLink() const { } } }); - return _rightActionLink; + return _rightAction->link; } ClickHandlerPtr Message::fastReplyLink() const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 8c86862998..f2c7693861 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -130,6 +130,7 @@ public: bool displayFastReply() const override; bool displayRightActionComments() const; std::optional rightActionSize() const override; + void applyRightActionLastPoint(QPoint p) const override; void drawRightAction( Painter &p, const PaintContext &context, @@ -168,6 +169,7 @@ protected: private: struct CommentsButton; struct FromNameStatus; + struct RightAction; void initLogEntryOriginal(); void initPsa(); @@ -186,6 +188,9 @@ private: void toggleTopicButtonRipple(bool pressed); void createTopicButtonRipple(); + void toggleRightActionRipple(bool pressed); + void createRightActionRipple(); + void paintCommentsButton( Painter &p, QRect &g, @@ -288,7 +293,7 @@ private: void refreshReactions(); void validateFromNameText(PeerData *from) const; - mutable ClickHandlerPtr _rightActionLink; + mutable std::unique_ptr _rightAction; mutable ClickHandlerPtr _fastReplyLink; mutable std::unique_ptr _viewButton; std::unique_ptr _reactions; diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index fb2941de31..180f569394 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/click_handler.h" #include "ui/effects/radial_animation.h" +#include "ui/effects/ripple_animation.h" #include "ui/painter.h" #include "api/api_transcribes.h" #include "apiwrap.h" @@ -68,12 +69,25 @@ void TranscribeButton::paint( const auto stm = context.messageStyle(); if (_roundview) { _lastPaintedPoint = { x, y }; + const auto r = QRect(QPoint(x, y), size()); + + if (_ripple) { + const auto colorOverride = &stm->msgWaveformInactive->c; + _ripple->paint( + p, + x, + y, + r.width(), + colorOverride); + if (_ripple->empty()) { + _ripple.reset(); + } + } PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(context.st->msgServiceBg()); - const auto r = QRect(QPoint(x, y), size()); p.drawEllipse(r); context.st->historyFastTranscribeIcon().paintInCenter(p, r); @@ -195,8 +209,25 @@ ClickHandlerPtr TranscribeButton::link() { return _link; } -QRect TranscribeButton::lastPaintedRect() const { - return { _lastPaintedPoint, size() }; +bool TranscribeButton::contains(const QPoint &p) { + _lastStatePoint = p - _lastPaintedPoint; + return QRect(_lastPaintedPoint, size()).contains(p); +} + +void TranscribeButton::addRipple(Fn callback) { + if (!_ripple) { + _ripple = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::EllipseMask(size()), + std::move(callback)); + } + _ripple->add(_lastStatePoint); +} + +void TranscribeButton::stopRipple() const { + if (_ripple) { + _ripple->lastStop(); + } } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h index 1765d80d31..6fd26c52a7 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { struct ChatPaintContext; class InfiniteRadialAnimation; +class RippleAnimation; } // namespace Ui namespace HistoryView { @@ -28,9 +29,11 @@ public: void setOpened(bool opened, Fn update); void setLoading(bool loading, Fn update); void paint(QPainter &p, int x, int y, const PaintContext &context); + void addRipple(Fn callback); + void stopRipple() const; [[nodiscard]] ClickHandlerPtr link(); - [[nodiscard]] QRect lastPaintedRect() const; + [[nodiscard]] bool contains(const QPoint &p); private: const not_null _item; @@ -38,12 +41,14 @@ private: const QSize _size; mutable std::unique_ptr _animation; + std::unique_ptr _ripple; ClickHandlerPtr _link; QString _text; Ui::Animations::Simple _openedAnimation; bool _loading = false; bool _opened = false; QPoint _lastPaintedPoint; + QPoint _lastStatePoint; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index d580e9393e..063c1833ee 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -394,6 +394,8 @@ TextState ExtendedPreview::textState(QPoint point, StateRequest request) const { auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 521be27886..e77cf2f790 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1033,15 +1033,32 @@ TextState Gif::textState(QPoint point, StateRequest request) const { } if (QRect(QPoint(fastShareLeft, fastShareTop), *size).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } - if (_transcribe && _transcribe->lastPaintedRect().contains(point)) { + if (_transcribe && _transcribe->contains(point)) { result.link = _transcribe->link(); } } return result; } +void Gif::clickHandlerPressedChanged( + const ClickHandlerPtr &handler, + bool pressed) { + File::clickHandlerPressedChanged(handler, pressed); + if (!handler) { + return; + } else if (_transcribe && (handler == _transcribe->link())) { + if (pressed) { + _transcribe->addRipple([=] { repaint(); }); + } else { + _transcribe->stopRipple(); + } + } +} + TextForMimeData Gif::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 270fa8aad7..ad3e47aac0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -50,6 +50,10 @@ public: void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; + void clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) override; + [[nodiscard]] TextSelection adjustSelection( TextSelection selection, TextSelectType type) const override { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index f111b4ae02..93b1f8e937 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -335,6 +335,8 @@ TextState Location::textState(QPoint point, StateRequest request) const { auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index cb9854dac8..5268090376 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -462,6 +462,8 @@ TextState GroupedMedia::textState(QPoint point, StateRequest request) const { auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } } 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 516dfe0380..c922e4fe7a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -486,6 +486,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { *rightActionSize); if (QRect(position.x(), position.y(), rightActionSize->width(), rightActionSize->height()).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point - position); return result; } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 73913a1989..2efe066c39 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -566,6 +566,8 @@ TextState Photo::textState(QPoint point, StateRequest request) const { auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { result.link = _parent->rightActionLink(); + _parent->applyRightActionLastPoint(point + - QPoint(fastShareLeft, fastShareTop)); } } }