2016-11-15 11:56:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-11-15 11:56:49 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-11-15 11:56:49 +00:00
|
|
|
*/
|
|
|
|
#include "ui/effects/ripple_animation.h"
|
|
|
|
|
|
|
|
namespace Ui {
|
|
|
|
|
|
|
|
class RippleAnimation::Ripple {
|
|
|
|
public:
|
2016-11-20 12:54:07 +00:00
|
|
|
Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update);
|
|
|
|
Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update);
|
2016-11-15 11:56:49 +00:00
|
|
|
|
2016-12-02 19:16:35 +00:00
|
|
|
void paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride);
|
2016-11-15 11:56:49 +00:00
|
|
|
|
|
|
|
void stop();
|
2016-11-16 12:06:02 +00:00
|
|
|
void unstop();
|
|
|
|
void finish();
|
2016-11-15 11:56:49 +00:00
|
|
|
bool finished() const {
|
|
|
|
return _hiding && !_hide.animating();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const style::RippleAnimation &_st;
|
|
|
|
UpdateCallback _update;
|
|
|
|
|
|
|
|
QPoint _origin;
|
|
|
|
int _radiusFrom = 0;
|
|
|
|
int _radiusTo = 0;
|
|
|
|
|
|
|
|
bool _hiding = false;
|
2016-12-07 13:32:25 +00:00
|
|
|
Animation _show;
|
|
|
|
Animation _hide;
|
2016-11-15 11:56:49 +00:00
|
|
|
QPixmap _cache;
|
|
|
|
QImage _frame;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2016-11-20 12:54:07 +00:00
|
|
|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update)
|
2016-11-15 11:56:49 +00:00
|
|
|
: _st(st)
|
|
|
|
, _update(update)
|
|
|
|
, _origin(origin)
|
|
|
|
, _radiusFrom(startRadius)
|
|
|
|
, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
|
|
|
|
_frame.setDevicePixelRatio(mask.devicePixelRatio());
|
|
|
|
|
|
|
|
QPoint points[] = {
|
|
|
|
{ 0, 0 },
|
|
|
|
{ _frame.width() / cIntRetinaFactor(), 0 },
|
|
|
|
{ _frame.width() / cIntRetinaFactor(), _frame.height() / cIntRetinaFactor() },
|
|
|
|
{ 0, _frame.height() / cIntRetinaFactor() },
|
|
|
|
};
|
|
|
|
for (auto point : points) {
|
|
|
|
accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point));
|
|
|
|
}
|
|
|
|
_radiusTo = qRound(sqrt(_radiusTo));
|
|
|
|
|
2017-01-11 08:16:44 +00:00
|
|
|
_show.start(UpdateCallback(_update), 0., 1., _st.showDuration, anim::easeOutQuint);
|
2016-11-15 11:56:49 +00:00
|
|
|
}
|
|
|
|
|
2016-11-20 12:54:07 +00:00
|
|
|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update)
|
2016-11-16 12:06:02 +00:00
|
|
|
: _st(st)
|
|
|
|
, _update(update)
|
|
|
|
, _origin(mask.width() / (2 * cIntRetinaFactor()), mask.height() / (2 * cIntRetinaFactor()))
|
|
|
|
, _radiusFrom(mask.width() + mask.height())
|
|
|
|
, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
|
|
|
|
_frame.setDevicePixelRatio(mask.devicePixelRatio());
|
|
|
|
_radiusTo = _radiusFrom;
|
|
|
|
_hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration);
|
|
|
|
}
|
|
|
|
|
2016-12-02 19:16:35 +00:00
|
|
|
void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride) {
|
2016-11-15 11:56:49 +00:00
|
|
|
auto opacity = _hide.current(ms, _hiding ? 0. : 1.);
|
|
|
|
if (opacity == 0.) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-05 11:01:08 +00:00
|
|
|
if (_cache.isNull() || colorOverride != nullptr) {
|
2016-11-15 11:56:49 +00:00
|
|
|
auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.current(ms, 1.));
|
|
|
|
_frame.fill(Qt::transparent);
|
|
|
|
{
|
|
|
|
Painter p(&_frame);
|
|
|
|
p.setPen(Qt::NoPen);
|
2016-12-02 19:16:35 +00:00
|
|
|
if (colorOverride) {
|
|
|
|
p.setBrush(*colorOverride);
|
|
|
|
} else {
|
|
|
|
p.setBrush(_st.color);
|
|
|
|
}
|
2016-12-03 12:10:35 +00:00
|
|
|
{
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
p.drawEllipse(_origin, radius, radius);
|
|
|
|
}
|
2016-11-15 11:56:49 +00:00
|
|
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
|
|
p.drawPixmap(0, 0, mask);
|
|
|
|
}
|
2016-12-05 11:01:08 +00:00
|
|
|
if (radius == _radiusTo && colorOverride == nullptr) {
|
2017-02-21 13:45:56 +00:00
|
|
|
_cache = App::pixmapFromImageInPlace(std::move(_frame));
|
2016-11-15 11:56:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
auto saved = p.opacity();
|
|
|
|
if (opacity != 1.) p.setOpacity(saved * opacity);
|
|
|
|
if (_cache.isNull()) {
|
|
|
|
p.drawImage(0, 0, _frame);
|
|
|
|
} else {
|
|
|
|
p.drawPixmap(0, 0, _cache);
|
|
|
|
}
|
|
|
|
if (opacity != 1.) p.setOpacity(saved);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RippleAnimation::Ripple::stop() {
|
|
|
|
_hiding = true;
|
|
|
|
_hide.start(UpdateCallback(_update), 1., 0., _st.hideDuration);
|
|
|
|
}
|
|
|
|
|
2016-11-16 12:06:02 +00:00
|
|
|
void RippleAnimation::Ripple::unstop() {
|
|
|
|
if (_hiding) {
|
|
|
|
if (_hide.animating()) {
|
|
|
|
_hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration);
|
|
|
|
}
|
|
|
|
_hiding = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RippleAnimation::Ripple::finish() {
|
|
|
|
if (_update) {
|
|
|
|
_update();
|
|
|
|
}
|
|
|
|
_show.finish();
|
|
|
|
_hide.finish();
|
|
|
|
}
|
|
|
|
|
2016-11-20 12:54:07 +00:00
|
|
|
RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, const UpdateCallback &callback)
|
2016-11-15 11:56:49 +00:00
|
|
|
: _st(st)
|
2017-02-21 13:45:56 +00:00
|
|
|
, _mask(App::pixmapFromImageInPlace(std::move(mask)))
|
2016-11-20 12:54:07 +00:00
|
|
|
, _update(callback) {
|
2016-11-15 11:56:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RippleAnimation::add(QPoint origin, int startRadius) {
|
2016-11-16 12:06:02 +00:00
|
|
|
lastStop();
|
2016-11-15 11:56:49 +00:00
|
|
|
_ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update));
|
|
|
|
}
|
|
|
|
|
2016-11-16 12:06:02 +00:00
|
|
|
void RippleAnimation::addFading() {
|
|
|
|
lastStop();
|
|
|
|
_ripples.push_back(new Ripple(_st, _mask, _update));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RippleAnimation::lastStop() {
|
2016-11-15 11:56:49 +00:00
|
|
|
if (!_ripples.isEmpty()) {
|
|
|
|
_ripples.back()->stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-16 12:06:02 +00:00
|
|
|
void RippleAnimation::lastUnstop() {
|
|
|
|
if (!_ripples.isEmpty()) {
|
|
|
|
_ripples.back()->unstop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RippleAnimation::lastFinish() {
|
|
|
|
if (!_ripples.isEmpty()) {
|
|
|
|
_ripples.back()->finish();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-02 19:16:35 +00:00
|
|
|
void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) {
|
2016-11-15 11:56:49 +00:00
|
|
|
if (_ripples.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rtl()) x = outerWidth - x - (_mask.width() / cIntRetinaFactor());
|
|
|
|
p.translate(x, y);
|
|
|
|
for (auto ripple : _ripples) {
|
2016-12-02 19:16:35 +00:00
|
|
|
ripple->paint(p, _mask, ms, colorOverride);
|
2016-11-15 11:56:49 +00:00
|
|
|
}
|
|
|
|
p.translate(-x, -y);
|
|
|
|
clearFinished();
|
|
|
|
}
|
|
|
|
|
2017-02-26 11:32:13 +00:00
|
|
|
QImage RippleAnimation::maskByDrawer(QSize size, bool filled, base::lambda<void(QPainter &p)> drawer) {
|
2016-11-16 16:04:25 +00:00
|
|
|
auto result = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
2016-12-05 08:45:56 +00:00
|
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
2016-11-16 16:04:25 +00:00
|
|
|
result.fill(filled ? QColor(255, 255, 255) : Qt::transparent);
|
|
|
|
if (drawer) {
|
|
|
|
Painter p(&result);
|
2016-12-03 12:10:35 +00:00
|
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
|
2016-11-16 16:04:25 +00:00
|
|
|
p.setPen(Qt::NoPen);
|
|
|
|
p.setBrush(QColor(255, 255, 255));
|
|
|
|
drawer(p);
|
|
|
|
}
|
2017-02-21 14:37:53 +00:00
|
|
|
return result;
|
2016-11-16 16:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QImage RippleAnimation::rectMask(QSize size) {
|
2016-11-20 12:54:07 +00:00
|
|
|
return maskByDrawer(size, true, base::lambda<void(QPainter&)>());
|
2016-11-16 16:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QImage RippleAnimation::roundRectMask(QSize size, int radius) {
|
|
|
|
return maskByDrawer(size, false, [size, radius](QPainter &p) {
|
|
|
|
p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
QImage RippleAnimation::ellipseMask(QSize size) {
|
|
|
|
return maskByDrawer(size, false, [size](QPainter &p) {
|
|
|
|
p.drawEllipse(0, 0, size.width(), size.height());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-11-15 11:56:49 +00:00
|
|
|
void RippleAnimation::clearFinished() {
|
|
|
|
while (!_ripples.isEmpty() && _ripples.front()->finished()) {
|
|
|
|
delete base::take(_ripples.front());
|
|
|
|
_ripples.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RippleAnimation::clear() {
|
|
|
|
for (auto ripple : base::take(_ripples)) {
|
|
|
|
delete ripple;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ui
|