243 lines
6.6 KiB
C++
243 lines
6.6 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "ui/effects/ripple_animation.h"
|
|
|
|
namespace Ui {
|
|
|
|
class RippleAnimation::Ripple {
|
|
public:
|
|
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);
|
|
|
|
void paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride);
|
|
|
|
void stop();
|
|
void unstop();
|
|
void finish();
|
|
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;
|
|
Animation _show;
|
|
Animation _hide;
|
|
QPixmap _cache;
|
|
QImage _frame;
|
|
|
|
};
|
|
|
|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update)
|
|
: _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));
|
|
|
|
_show.start(UpdateCallback(_update), 0., 1., _st.showDuration, anim::easeOutCirc);
|
|
}
|
|
|
|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update)
|
|
: _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);
|
|
}
|
|
|
|
void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride) {
|
|
auto opacity = _hide.current(ms, _hiding ? 0. : 1.);
|
|
if (opacity == 0.) {
|
|
return;
|
|
}
|
|
|
|
if (_cache.isNull() || colorOverride != nullptr) {
|
|
auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.current(ms, 1.));
|
|
_frame.fill(Qt::transparent);
|
|
{
|
|
Painter p(&_frame);
|
|
p.setPen(Qt::NoPen);
|
|
if (colorOverride) {
|
|
p.setBrush(*colorOverride);
|
|
} else {
|
|
p.setBrush(_st.color);
|
|
}
|
|
{
|
|
PainterHighQualityEnabler hq(p);
|
|
p.drawEllipse(_origin, radius, radius);
|
|
}
|
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
p.drawPixmap(0, 0, mask);
|
|
}
|
|
if (radius == _radiusTo && colorOverride == nullptr) {
|
|
_cache = App::pixmapFromImageInPlace(std_::move(_frame));
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, const UpdateCallback &callback)
|
|
: _st(st)
|
|
, _mask(App::pixmapFromImageInPlace(std_::move(mask)))
|
|
, _update(callback) {
|
|
}
|
|
|
|
|
|
void RippleAnimation::add(QPoint origin, int startRadius) {
|
|
lastStop();
|
|
_ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update));
|
|
}
|
|
|
|
void RippleAnimation::addFading() {
|
|
lastStop();
|
|
_ripples.push_back(new Ripple(_st, _mask, _update));
|
|
}
|
|
|
|
void RippleAnimation::lastStop() {
|
|
if (!_ripples.isEmpty()) {
|
|
_ripples.back()->stop();
|
|
}
|
|
}
|
|
|
|
void RippleAnimation::lastUnstop() {
|
|
if (!_ripples.isEmpty()) {
|
|
_ripples.back()->unstop();
|
|
}
|
|
}
|
|
|
|
void RippleAnimation::lastFinish() {
|
|
if (!_ripples.isEmpty()) {
|
|
_ripples.back()->finish();
|
|
}
|
|
}
|
|
|
|
void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) {
|
|
if (_ripples.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (rtl()) x = outerWidth - x - (_mask.width() / cIntRetinaFactor());
|
|
p.translate(x, y);
|
|
for (auto ripple : _ripples) {
|
|
ripple->paint(p, _mask, ms, colorOverride);
|
|
}
|
|
p.translate(-x, -y);
|
|
clearFinished();
|
|
}
|
|
|
|
QImage RippleAnimation::maskByDrawer(QSize size, bool filled, base::lambda<void(QPainter &p)> &&drawer) {
|
|
auto result = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
|
result.fill(filled ? QColor(255, 255, 255) : Qt::transparent);
|
|
if (drawer) {
|
|
Painter p(&result);
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(QColor(255, 255, 255));
|
|
drawer(p);
|
|
}
|
|
return std_::move(result);
|
|
}
|
|
|
|
QImage RippleAnimation::rectMask(QSize size) {
|
|
return maskByDrawer(size, true, base::lambda<void(QPainter&)>());
|
|
}
|
|
|
|
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());
|
|
});
|
|
}
|
|
|
|
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
|