2022-02-08 19:19:01 +00:00
|
|
|
/*
|
|
|
|
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 {
|
|
|
|
|
2022-02-08 23:16:17 +00:00
|
|
|
constexpr auto kSurroundingProgress = 0.5;
|
|
|
|
|
|
|
|
inline float64 OffsetMid(int value, float64 min, float64 max = 1.) {
|
|
|
|
return ((value * max) - (value * min)) / 2.;
|
|
|
|
}
|
|
|
|
|
2022-02-08 19:19:01 +00:00
|
|
|
class Content final : public RpWidget {
|
|
|
|
public:
|
|
|
|
Content(
|
|
|
|
not_null<RpWidget*> parent,
|
|
|
|
not_null<Window::SessionController*> controller,
|
|
|
|
QRect globalGeometryFrom,
|
|
|
|
MessageSendingAnimationController::SendingInfoTo &&to);
|
|
|
|
|
|
|
|
[[nodiscard]] rpl::producer<> destroyRequests() const;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
|
|
|
|
private:
|
2022-02-08 23:16:17 +00:00
|
|
|
using Context = Ui::ChatPaintContext;
|
|
|
|
|
|
|
|
QImage drawMedia(
|
|
|
|
Context::SkipDrawingParts skipParts,
|
|
|
|
const QRect &rect) const;
|
2022-02-08 19:19:01 +00:00
|
|
|
void updateCache();
|
2022-02-10 19:18:07 +00:00
|
|
|
void createSurrounding();
|
2022-02-08 19:19:01 +00:00
|
|
|
|
|
|
|
const not_null<Window::SessionController*> _controller;
|
2022-02-10 18:53:51 +00:00
|
|
|
Fn<not_null<HistoryView::Element*>()> _view;
|
2022-02-08 19:19:01 +00:00
|
|
|
not_null<ChatTheme*> _theme;
|
|
|
|
QImage _cache;
|
|
|
|
QRect _from;
|
|
|
|
QRect _to;
|
|
|
|
|
|
|
|
Animations::Simple _animation;
|
|
|
|
float64 _minScale = 0;
|
|
|
|
|
2022-02-10 19:18:07 +00:00
|
|
|
base::unique_qptr<Ui::RpWidget> _surrounding;
|
2022-02-08 23:16:17 +00:00
|
|
|
|
2022-02-08 19:19:01 +00:00
|
|
|
rpl::event_stream<> _destroyRequests;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
Content::Content(
|
|
|
|
not_null<RpWidget*> parent,
|
|
|
|
not_null<Window::SessionController*> controller,
|
|
|
|
QRect globalGeometryFrom,
|
|
|
|
MessageSendingAnimationController::SendingInfoTo &&to)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _controller(controller)
|
2022-02-10 18:53:51 +00:00
|
|
|
, _view(std::move(to.view))
|
2022-02-08 19:19:01 +00:00
|
|
|
, _theme(to.theme)
|
|
|
|
, _from(parent->mapFromGlobal(globalGeometryFrom)) {
|
2022-02-10 18:53:51 +00:00
|
|
|
Expects(_view != nullptr);
|
2022-02-08 19:19:01 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
|
2022-02-08 23:16:17 +00:00
|
|
|
const auto innerContentRect
|
2022-02-10 18:53:51 +00:00
|
|
|
= _view()->media()->contentRectForReactions();
|
2022-02-08 19:19:01 +00:00
|
|
|
auto animationCallback = [=](float64 value) {
|
|
|
|
auto resultFrom = QRect(
|
|
|
|
QPoint(),
|
|
|
|
_cache.size() / style::DevicePixelRatio());
|
|
|
|
resultFrom.moveCenter(_from.center());
|
|
|
|
|
|
|
|
const auto resultTo = _to.topLeft() + innerContentRect.topLeft();
|
2022-02-08 23:16:17 +00:00
|
|
|
const auto x = anim::interpolate(resultFrom.x(), resultTo.x(), value);
|
|
|
|
const auto y = anim::interpolate(resultFrom.y(), resultTo.y(), value);
|
|
|
|
moveToLeft(x, y);
|
2022-02-08 19:19:01 +00:00
|
|
|
update();
|
|
|
|
|
2022-02-08 23:16:17 +00:00
|
|
|
if ((value > kSurroundingProgress) && !_surrounding) {
|
2022-02-10 19:18:07 +00:00
|
|
|
createSurrounding();
|
2022-02-08 23:16:17 +00:00
|
|
|
}
|
|
|
|
if (_surrounding) {
|
|
|
|
_surrounding->moveToLeft(
|
|
|
|
x - innerContentRect.x(),
|
|
|
|
y - innerContentRect.y());
|
|
|
|
}
|
|
|
|
|
2022-02-08 19:19:01 +00:00
|
|
|
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(
|
2022-02-08 23:16:17 +00:00
|
|
|
(1 - progress) * OffsetMid(size.width(), _minScale),
|
|
|
|
(1 - progress) * OffsetMid(size.height(), _minScale));
|
2022-02-08 19:19:01 +00:00
|
|
|
p.scale(scale, scale);
|
|
|
|
p.drawImage(QPoint(), _cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<> Content::destroyRequests() const {
|
|
|
|
return _destroyRequests.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Content::updateCache() {
|
2022-02-08 23:16:17 +00:00
|
|
|
_cache = drawMedia(
|
|
|
|
Context::SkipDrawingParts::Surrounding,
|
2022-02-10 18:53:51 +00:00
|
|
|
_view()->media()->contentRectForReactions());
|
2022-02-08 23:16:17 +00:00
|
|
|
resize(_cache.size() / style::DevicePixelRatio());
|
|
|
|
}
|
|
|
|
|
|
|
|
QImage Content::drawMedia(
|
|
|
|
Context::SkipDrawingParts skipParts,
|
|
|
|
const QRect &rect) const {
|
|
|
|
auto image = QImage(
|
|
|
|
rect.size() * style::DevicePixelRatio(),
|
2022-02-08 19:19:01 +00:00
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
2022-02-08 23:16:17 +00:00
|
|
|
image.setDevicePixelRatio(style::DevicePixelRatio());
|
|
|
|
image.fill(Qt::transparent);
|
2022-02-08 19:19:01 +00:00
|
|
|
{
|
2022-02-08 23:16:17 +00:00
|
|
|
Painter p(&image);
|
2022-02-08 19:19:01 +00:00
|
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
|
|
|
|
auto context = _controller->preparePaintContext({
|
|
|
|
.theme = _theme,
|
2022-02-08 23:16:17 +00:00
|
|
|
});
|
2022-02-10 18:53:51 +00:00
|
|
|
const auto view = _view();
|
2022-02-08 23:16:17 +00:00
|
|
|
context.skipDrawingParts = skipParts;
|
2022-02-10 18:53:51 +00:00
|
|
|
context.outbg = view->hasOutLayout();
|
2022-02-08 23:16:17 +00:00
|
|
|
p.translate(-rect.left(), -rect.top());
|
2022-02-10 18:53:51 +00:00
|
|
|
view->media()->draw(p, context);
|
2022-02-08 19:19:01 +00:00
|
|
|
}
|
2022-02-08 23:16:17 +00:00
|
|
|
return image;
|
2022-02-08 19:19:01 +00:00
|
|
|
}
|
|
|
|
|
2022-02-10 19:18:07 +00:00
|
|
|
void Content::createSurrounding() {
|
|
|
|
_surrounding = base::make_unique_q<Ui::RpWidget>(parentWidget());
|
|
|
|
_surrounding->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
|
|
|
|
const auto view = _view();
|
|
|
|
const auto surroundingSize = view->innerGeometry().size();
|
|
|
|
const auto offset = view->media()->contentRectForReactions().topLeft();
|
|
|
|
|
|
|
|
_surrounding->resize(surroundingSize);
|
|
|
|
_surrounding->show();
|
|
|
|
|
|
|
|
// Do not raise.
|
|
|
|
_surrounding->stackUnder(this);
|
|
|
|
stackUnder(_surrounding.get());
|
|
|
|
|
|
|
|
_surrounding->paintRequest(
|
|
|
|
) | rpl::start_with_next([=, size = surroundingSize](const QRect &r) {
|
|
|
|
Painter p(_surrounding);
|
|
|
|
|
|
|
|
p.fillRect(r, Qt::transparent);
|
|
|
|
|
|
|
|
const auto progress = _animation.value(0.);
|
|
|
|
const auto revProgress = 1. - progress;
|
|
|
|
|
|
|
|
const auto divider = 1. - kSurroundingProgress;
|
|
|
|
const auto alpha = (divider - revProgress) / divider;
|
|
|
|
p.setOpacity(alpha);
|
|
|
|
|
|
|
|
const auto scale = anim::interpolateF(_minScale, 1., progress);
|
|
|
|
|
|
|
|
p.translate(
|
|
|
|
revProgress * OffsetMid(size.width() + offset.x(), _minScale),
|
|
|
|
revProgress * OffsetMid(size.height() + offset.y(), _minScale));
|
|
|
|
p.scale(scale, scale);
|
|
|
|
|
|
|
|
auto context = _controller->preparePaintContext({
|
|
|
|
.theme = _theme,
|
|
|
|
});
|
|
|
|
|
|
|
|
const auto view = _view();
|
|
|
|
|
|
|
|
context.skipDrawingParts = Context::SkipDrawingParts::Content;
|
|
|
|
context.outbg = view->hasOutLayout();
|
|
|
|
|
|
|
|
view->media()->draw(p, context);
|
|
|
|
}, _surrounding->lifetime());
|
|
|
|
}
|
|
|
|
|
2022-02-08 19:19:01 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
MessageSendingAnimationController::MessageSendingAnimationController(
|
|
|
|
not_null<Window::SessionController*> 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();
|
2022-02-10 18:53:51 +00:00
|
|
|
const auto item = to.view()->data();
|
2022-02-08 19:19:01 +00:00
|
|
|
|
|
|
|
const auto it = _itemSendPending.find(item->fullId().msg);
|
|
|
|
if (it == end(_itemSendPending)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto msg = it->first;
|
|
|
|
|
|
|
|
auto content = base::make_unique_q<Content>(
|
|
|
|
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<HistoryItem*> item) const {
|
|
|
|
return _processing.contains(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ui
|