232 lines
7 KiB
C++
232 lines
7 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/widgets/call_button.h"
|
|
|
|
#include "ui/effects/ripple_animation.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "styles/style_widgets.h"
|
|
#include "styles/palette.h"
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr auto kOuterBounceDuration = crl::time(100);
|
|
|
|
} // namespace
|
|
|
|
CallButton::CallButton(
|
|
QWidget *parent,
|
|
const style::CallButton &stFrom,
|
|
const style::CallButton *stTo)
|
|
: RippleButton(parent, stFrom.button.ripple)
|
|
, _stFrom(&stFrom)
|
|
, _stTo(stTo) {
|
|
resize(_stFrom->button.width, _stFrom->button.height);
|
|
|
|
_bgMask = RippleAnimation::ellipseMask(QSize(_stFrom->bgSize, _stFrom->bgSize));
|
|
_bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg));
|
|
if (_stTo) {
|
|
Assert(_stFrom->button.width == _stTo->button.width);
|
|
Assert(_stFrom->button.height == _stTo->button.height);
|
|
Assert(_stFrom->bgPosition == _stTo->bgPosition);
|
|
Assert(_stFrom->bgSize == _stTo->bgSize);
|
|
|
|
_bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
|
|
_bg.setDevicePixelRatio(style::DevicePixelRatio());
|
|
_bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg));
|
|
_iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
|
|
_iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio());
|
|
_iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
|
|
_iconFrom.setDevicePixelRatio(style::DevicePixelRatio());
|
|
_iconFrom.fill(Qt::black);
|
|
{
|
|
QPainter p(&_iconFrom);
|
|
p.drawImage(
|
|
(_stFrom->bgSize
|
|
- _stFrom->button.icon.width()) / 2,
|
|
(_stFrom->bgSize
|
|
- _stFrom->button.icon.height()) / 2,
|
|
_stFrom->button.icon.instance(Qt::white));
|
|
}
|
|
_iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
|
|
_iconTo.setDevicePixelRatio(style::DevicePixelRatio());
|
|
_iconTo.fill(Qt::black);
|
|
{
|
|
QPainter p(&_iconTo);
|
|
p.drawImage(
|
|
(_stTo->bgSize
|
|
- _stTo->button.icon.width()) / 2,
|
|
(_stTo->bgSize
|
|
- _stTo->button.icon.height()) / 2,
|
|
_stTo->button.icon.instance(Qt::white));
|
|
}
|
|
_iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
|
|
_iconMixed.setDevicePixelRatio(style::DevicePixelRatio());
|
|
}
|
|
}
|
|
|
|
void CallButton::setOuterValue(float64 value) {
|
|
if (_outerValue != value) {
|
|
_outerAnimation.start([this] {
|
|
if (_progress == 0. || _progress == 1.) {
|
|
update();
|
|
}
|
|
}, _outerValue, value, kOuterBounceDuration);
|
|
_outerValue = value;
|
|
}
|
|
}
|
|
|
|
void CallButton::setText(rpl::producer<QString> text) {
|
|
_label.create(this, std::move(text), _stFrom->label);
|
|
_label->show();
|
|
rpl::combine(
|
|
sizeValue(),
|
|
_label->sizeValue()
|
|
) | rpl::start_with_next([=](QSize my, QSize label) {
|
|
_label->moveToLeft(
|
|
(my.width() - label.width()) / 2,
|
|
my.height() - label.height(),
|
|
my.width());
|
|
}, _label->lifetime());
|
|
}
|
|
|
|
void CallButton::setProgress(float64 progress) {
|
|
_progress = progress;
|
|
update();
|
|
}
|
|
|
|
void CallButton::paintEvent(QPaintEvent *e) {
|
|
QPainter p(this);
|
|
|
|
auto bgPosition = myrtlpoint(_stFrom->bgPosition);
|
|
auto paintFrom = (_progress == 0.) || !_stTo;
|
|
auto paintTo = !paintFrom && (_progress == 1.);
|
|
|
|
auto outerValue = _outerAnimation.value(_outerValue);
|
|
if (outerValue > 0.) {
|
|
auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress);
|
|
auto outerPixels = outerValue * outerRadius;
|
|
auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize));
|
|
outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels));
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
if (paintFrom) {
|
|
p.setBrush(_stFrom->outerBg);
|
|
} else if (paintTo) {
|
|
p.setBrush(_stTo->outerBg);
|
|
} else {
|
|
p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress));
|
|
}
|
|
p.setPen(Qt::NoPen);
|
|
p.drawEllipse(outerRect);
|
|
}
|
|
|
|
if (_bgOverride) {
|
|
const auto &s = _stFrom->bgSize;
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(*_bgOverride);
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s)));
|
|
} else if (paintFrom) {
|
|
p.drawPixmap(bgPosition, _bgFrom);
|
|
} else if (paintTo) {
|
|
p.drawPixmap(bgPosition, _bgTo);
|
|
} else {
|
|
style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg);
|
|
p.drawImage(bgPosition, _bg);
|
|
}
|
|
|
|
auto rippleColorInterpolated = QColor();
|
|
auto rippleColorOverride = &rippleColorInterpolated;
|
|
if (_rippleOverride) {
|
|
rippleColorOverride = &(*_rippleOverride);
|
|
} else if (paintFrom) {
|
|
rippleColorOverride = nullptr;
|
|
} else if (paintTo) {
|
|
rippleColorOverride = &_stTo->button.ripple.color->c;
|
|
} else {
|
|
rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
|
|
}
|
|
paintRipple(p, _stFrom->button.rippleAreaPosition, rippleColorOverride);
|
|
|
|
auto positionFrom = iconPosition(_stFrom);
|
|
if (paintFrom) {
|
|
const auto icon = &_stFrom->button.icon;
|
|
icon->paint(p, positionFrom, width());
|
|
} else {
|
|
auto positionTo = iconPosition(_stTo);
|
|
if (paintTo) {
|
|
_stTo->button.icon.paint(p, positionTo, width());
|
|
} else {
|
|
mixIconMasks();
|
|
style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed);
|
|
p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed);
|
|
}
|
|
}
|
|
}
|
|
|
|
QPoint CallButton::iconPosition(not_null<const style::CallButton*> st) const {
|
|
auto result = st->button.iconPosition;
|
|
if (result.x() < 0) {
|
|
result.setX((width() - st->button.icon.width()) / 2);
|
|
}
|
|
if (result.y() < 0) {
|
|
result.setY((height() - st->button.icon.height()) / 2);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void CallButton::mixIconMasks() {
|
|
_iconMixedMask.fill(Qt::black);
|
|
|
|
Painter p(&_iconMixedMask);
|
|
PainterHighQualityEnabler hq(p);
|
|
auto paintIconMask = [this, &p](const QImage &mask, float64 angle) {
|
|
auto skipFrom = _stFrom->bgSize / 2;
|
|
p.translate(skipFrom, skipFrom);
|
|
p.rotate(angle);
|
|
p.translate(-skipFrom, -skipFrom);
|
|
p.drawImage(0, 0, mask);
|
|
};
|
|
p.save();
|
|
paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress);
|
|
p.restore();
|
|
p.setOpacity(_progress);
|
|
paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress));
|
|
}
|
|
|
|
void CallButton::onStateChanged(State was, StateChangeSource source) {
|
|
RippleButton::onStateChanged(was, source);
|
|
|
|
auto over = isOver();
|
|
auto wasOver = static_cast<bool>(was & StateFlag::Over);
|
|
if (over != wasOver) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void CallButton::setColorOverrides(rpl::producer<CallButtonColors> &&colors) {
|
|
std::move(
|
|
colors
|
|
) | rpl::start_with_next([=](const CallButtonColors &c) {
|
|
_bgOverride = c.bg;
|
|
_rippleOverride = c.ripple;
|
|
update();
|
|
}, lifetime());
|
|
}
|
|
|
|
QPoint CallButton::prepareRippleStartPosition() const {
|
|
return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
|
|
}
|
|
|
|
QImage CallButton::prepareRippleMask() const {
|
|
return RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize));
|
|
}
|
|
|
|
} // namespace Ui
|