diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5694f2d09..8ba40c259 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1125,6 +1125,9 @@ PRIVATE ui/chat/choose_theme_controller.h ui/effects/fireworks_animation.cpp ui/effects/fireworks_animation.h + ui/effects/message_sending_animation_common.h + ui/effects/message_sending_animation_controller.cpp + ui/effects/message_sending_animation_controller.h ui/effects/round_checkbox.cpp ui/effects/round_checkbox.h ui/effects/send_action_animations.cpp 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 475390074..67e4a7e48 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -138,9 +138,12 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const { height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height) : _contentSize.height(); const auto inner = QRect(usex, usey, usew, useh); - _content->draw(p, context, inner); + if (context.skipDrawingParts != PaintContext::SkipDrawingParts::Content) { + _content->draw(p, context, inner); + } - if (!inWebPage) { + if (!inWebPage && (context.skipDrawingParts + != PaintContext::SkipDrawingParts::Surrounding)) { drawSurrounding(p, inner, context, via, reply, forwarded); } } diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 72d2f891e..49e629f93 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -135,6 +135,14 @@ struct ChatPaintContext { return result; } + // This is supported only in unwrapped media for now. + enum class SkipDrawingParts { + None, + Content, + Surrounding, + }; + SkipDrawingParts skipDrawingParts = SkipDrawingParts::None; + }; [[nodiscard]] int HistoryServiceMsgRadius(); diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h b/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h new file mode 100644 index 000000000..7b0b4e53d --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h @@ -0,0 +1,17 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +struct MessageSendingAnimationFrom { + std::optional localId; + QRect globalStartGeometry; +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp new file mode 100644 index 000000000..13091ad50 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp @@ -0,0 +1,217 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/effects/message_sending_animation_controller.h" + +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_list_widget.h" // kItemRevealDuration +#include "history/view/media/history_view_media.h" +#include "main/main_session.h" +#include "mainwidget.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/effects/animation_value.h" +#include "ui/effects/animation_value_f.h" +#include "ui/effects/animations.h" +#include "ui/rp_widget.h" +#include "window/window_session_controller.h" + +namespace Ui { +namespace { + +class Content final : public RpWidget { +public: + Content( + not_null parent, + not_null controller, + QRect globalGeometryFrom, + MessageSendingAnimationController::SendingInfoTo &&to); + + [[nodiscard]] rpl::producer<> destroyRequests() const; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void updateCache(); + + const not_null _controller; + not_null _item; + not_null _theme; + not_null _media; + QImage _cache; + QRect _from; + QRect _to; + + Animations::Simple _animation; + float64 _minScale = 0; + + rpl::event_stream<> _destroyRequests; + +}; + +Content::Content( + not_null parent, + not_null controller, + QRect globalGeometryFrom, + MessageSendingAnimationController::SendingInfoTo &&to) +: RpWidget(parent) +, _controller(controller) +, _item(to.item) +, _theme(to.theme) +, _media(_item->mainView()->media()) +, _from(parent->mapFromGlobal(globalGeometryFrom)) { + + show(); + setAttribute(Qt::WA_TransparentForMouseEvents); + raise(); + + base::take( + to.globalEndGeometry + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](const QRect &r) { + _to = parent->mapFromGlobal(r); + _minScale = float64(_from.height()) / _to.height(); + }, lifetime()); + + updateCache(); + + _controller->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + updateCache(); + }, lifetime()); + + const auto innerContentRect = _media->contentRectForReactions(); + auto animationCallback = [=](float64 value) { + auto resultFrom = QRect( + QPoint(), + _cache.size() / style::DevicePixelRatio()); + resultFrom.moveCenter(_from.center()); + + const auto resultTo = _to.topLeft() + innerContentRect.topLeft(); + moveToLeft( + anim::interpolate(resultFrom.x(), resultTo.x(), value), + anim::interpolate(resultFrom.y(), resultTo.y(), value)); + update(); + + if (value == 1.) { + _destroyRequests.fire({}); + } + }; + animationCallback(0.); + _animation.start( + std::move(animationCallback), + 0., + 1., + HistoryView::ListWidget::kItemRevealDuration); +} + +void Content::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(e->rect(), Qt::transparent); + + if (_cache.isNull()) { + return; + } + + const auto progress = _animation.value(_animation.animating() ? 0. : 1.); + + const auto scale = anim::interpolateF(_minScale, 1., progress); + + const auto size = _cache.size() / style::DevicePixelRatio(); + p.translate( + (1 - progress) * ((size.width() - (size.width() * _minScale)) / 2), + (1 - progress) * ((size.height() - (size.height() * _minScale)) / 2)); + p.scale(scale, scale); + p.drawImage(QPoint(), _cache); +} + +rpl::producer<> Content::destroyRequests() const { + return _destroyRequests.events(); +} + +void Content::updateCache() { + const auto innerContentRect = _media->contentRectForReactions(); + _cache = QImage( + innerContentRect.size() * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + _cache.setDevicePixelRatio(style::DevicePixelRatio()); + _cache.fill(Qt::transparent); + { + Painter p(&_cache); + PainterHighQualityEnabler hq(p); + + auto context = _controller->preparePaintContext({ + .theme = _theme, + }); + using Context = Ui::ChatPaintContext; + context.skipDrawingParts = Context::SkipDrawingParts::Surrounding; + context.outbg = true; + p.translate(-innerContentRect.left(), -innerContentRect.top()); + _media->draw(p, context); + } + resize( + _cache.width() / style::DevicePixelRatio(), + _cache.height() / style::DevicePixelRatio()); +} + +} // namespace + +MessageSendingAnimationController::MessageSendingAnimationController( + not_null controller) +: _controller(controller) { +} + +void MessageSendingAnimationController::appendSending( + MessageSendingAnimationFrom from) { + if (anim::Disabled()) { + return; + } + if (from.localId) { + _itemSendPending[*from.localId] = from.globalStartGeometry; + } +} + +void MessageSendingAnimationController::startAnimation(SendingInfoTo &&to) { + if (anim::Disabled()) { + return; + } + const auto container = _controller->content(); + const auto item = to.item; + + const auto it = _itemSendPending.find(item->fullId().msg); + if (it == end(_itemSendPending)) { + return; + } + const auto msg = it->first; + + auto content = base::make_unique_q( + container, + _controller, + it->second, + std::move(to)); + content->destroyRequests( + ) | rpl::start_with_next([=] { + _itemSendPending.erase(msg); + _processing.erase(item); + }, content->lifetime()); + + _processing.emplace(item, std::move(content)); +} + +bool MessageSendingAnimationController::hasLocalMessage(MsgId msgId) const { + return _itemSendPending.contains(msgId); +} + +bool MessageSendingAnimationController::hasAnimatedMessage( + not_null item) const { + return _processing.contains(item); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h new file mode 100644 index 000000000..0b2f784fc --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h @@ -0,0 +1,50 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/unique_qptr.h" +#include "ui/effects/message_sending_animation_common.h" + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { + +class RpWidget; +class ChatTheme; + +class MessageSendingAnimationController final { +public: + explicit MessageSendingAnimationController( + not_null controller); + + struct SendingInfoTo { + rpl::producer globalEndGeometry; + not_null item; + not_null theme; + }; + + void appendSending(MessageSendingAnimationFrom from); + void startAnimation(SendingInfoTo &&to); + + [[nodiscard]] bool hasLocalMessage(MsgId msgId) const; + [[nodiscard]] bool hasAnimatedMessage(not_null item) const; + +private: + + const not_null _controller; + base::flat_map _itemSendPending; + + base::flat_map< + not_null, + base::unique_qptr> _processing; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index ab49f8873..9876178d4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" +#include "ui/effects/message_sending_animation_controller.h" #include "ui/style/style_palette_colorizer.h" #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" @@ -526,6 +527,8 @@ SessionController::SessionController( , _window(window) , _emojiInteractions( std::make_unique(session)) +, _sendingAnimation( + std::make_unique(this)) , _tabbedSelector( std::make_unique( _window->widget(), @@ -627,6 +630,11 @@ not_null<::MainWindow*> SessionController::widget() const { return _window->widget(); } +auto SessionController::sendingAnimation() const +-> Ui::MessageSendingAnimationController & { + return *_sendingAnimation; +} + auto SessionController::tabbedSelector() const -> not_null { return _tabbedSelector.get(); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 890e82318..05d144261 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -50,6 +50,7 @@ struct ChatThemeKey; struct ChatPaintContext; struct ChatThemeBackground; struct ChatThemeBackgroundData; +class MessageSendingAnimationController; } // namespace Ui namespace Data { @@ -279,6 +280,8 @@ public: Ui::LayerOptions options = Ui::LayerOption::KeepOther, anim::type animated = anim::type::normal); + [[nodiscard]] auto sendingAnimation() const + -> Ui::MessageSendingAnimationController &; [[nodiscard]] auto tabbedSelector() const -> not_null; void takeTabbedSelectorOwnershipFrom(not_null parent); @@ -509,6 +512,9 @@ private: const not_null _window; const std::unique_ptr _emojiInteractions; + using SendingAnimation = Ui::MessageSendingAnimationController; + const std::unique_ptr _sendingAnimation; + std::unique_ptr _passportForm; std::unique_ptr _filters;