init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
281
Telegram/lib_ui/ui/widgets/call_button.cpp
Normal file
281
Telegram/lib_ui/ui/widgets/call_button.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
// 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 "ui/qt_object_factory.h"
|
||||
#include "ui/ui_utility.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) {
|
||||
init();
|
||||
}
|
||||
|
||||
void CallButton::init() {
|
||||
resize(_stFrom->button.width, _stFrom->button.height);
|
||||
|
||||
const auto size = QSize(_stFrom->bgSize, _stFrom->bgSize);
|
||||
_bgMask = RippleAnimation::MaskByDrawer(size, false, [&](QPainter &p) {
|
||||
p.drawEllipse(0, 0, size.width(), size.height());
|
||||
if (_corner) {
|
||||
auto position = _corner->pos() - _stFrom->bgPosition;
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setBrush(st::transparent);
|
||||
const auto border = _stFrom->cornerButtonBorder;
|
||||
p.drawEllipse(QRect(position, _corner->size()).marginsAdded(
|
||||
{ border, border, border, border }));
|
||||
}
|
||||
});
|
||||
_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::on_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;
|
||||
if (_corner) {
|
||||
_corner->setProgress(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) {
|
||||
Assert(!_corner); // Didn't support this case yet.
|
||||
|
||||
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::on_next([=](const CallButtonColors &c) {
|
||||
_bgOverride = c.bg;
|
||||
_rippleOverride = c.ripple;
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void CallButton::setStyle(
|
||||
const style::CallButton &stFrom,
|
||||
const style::CallButton *stTo) {
|
||||
if (_stFrom == &stFrom && _stTo == stTo) {
|
||||
return;
|
||||
}
|
||||
_stFrom = &stFrom;
|
||||
_stTo = stTo;
|
||||
init();
|
||||
update();
|
||||
}
|
||||
|
||||
not_null<CallButton*> CallButton::addCornerButton(
|
||||
const style::CallButton &stFrom,
|
||||
const style::CallButton *stTo) {
|
||||
Expects(!_corner);
|
||||
|
||||
_corner = CreateChild<CallButton>(this, stFrom, stTo);
|
||||
_corner->move(_stFrom->cornerButtonPosition);
|
||||
_corner->setProgress(_progress);
|
||||
_corner->show();
|
||||
init();
|
||||
update();
|
||||
|
||||
return _corner;
|
||||
}
|
||||
|
||||
QPoint CallButton::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
|
||||
}
|
||||
|
||||
QImage CallButton::prepareRippleMask() const {
|
||||
return _bgMask;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
Reference in New Issue
Block a user