263 lines
6.4 KiB
C++
263 lines
6.4 KiB
C++
/*
|
|
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 "history/view/controls/history_view_voice_record_button.h"
|
|
|
|
#include "ui/paint/blobs.h"
|
|
|
|
#include "styles/style_chat.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace HistoryView::Controls {
|
|
|
|
namespace {
|
|
|
|
constexpr auto kMaxLevel = 1800.;
|
|
constexpr auto kBlobAlpha = 76. / 255.;
|
|
constexpr auto kBlobMaxSpeed = 5.0;
|
|
constexpr auto kLevelDuration = 100. + 500. * 0.33;
|
|
constexpr auto kBlobsScaleEnterDuration = crl::time(250);
|
|
|
|
auto Blobs() {
|
|
return std::vector<Ui::Paint::Blobs::BlobData>{
|
|
{
|
|
.segmentsCount = 9,
|
|
.minScale = 0.605229,
|
|
.minRadius = (float)st::historyRecordMinorBlobMinRadius,
|
|
.maxRadius = (float)st::historyRecordMinorBlobMaxRadius,
|
|
.speedScale = 1.,
|
|
.alpha = kBlobAlpha,
|
|
.maxSpeed = kBlobMaxSpeed,
|
|
},
|
|
{
|
|
.segmentsCount = 12,
|
|
.minScale = 0.553943,
|
|
.minRadius = (float)st::historyRecordMajorBlobMinRadius,
|
|
.maxRadius = (float)st::historyRecordMajorBlobMaxRadius,
|
|
.speedScale = 1.,
|
|
.alpha = kBlobAlpha,
|
|
.maxSpeed = kBlobMaxSpeed,
|
|
},
|
|
};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
VoiceRecordButton::VoiceRecordButton(
|
|
not_null<Ui::RpWidget*> parent,
|
|
rpl::producer<> leaveWindowEventProducer)
|
|
: AbstractButton(parent)
|
|
, _blobs(std::make_unique<Ui::Paint::Blobs>(
|
|
Blobs(),
|
|
kLevelDuration,
|
|
kMaxLevel))
|
|
, _center(_blobs->maxRadius()) {
|
|
resize(_center * 2, _center * 2);
|
|
std::move(
|
|
leaveWindowEventProducer
|
|
) | rpl::start_with_next([=] {
|
|
_inCircle = false;
|
|
}, lifetime());
|
|
init();
|
|
}
|
|
|
|
VoiceRecordButton::~VoiceRecordButton() = default;
|
|
|
|
void VoiceRecordButton::requestPaintLevel(quint16 level) {
|
|
if (_blobsHideLastTime) {
|
|
return;
|
|
}
|
|
_blobs->setLevel(level);
|
|
update();
|
|
}
|
|
|
|
void VoiceRecordButton::init() {
|
|
const auto currentState = lifetime().make_state<Type>(_state.current());
|
|
|
|
rpl::single(
|
|
anim::Disabled()
|
|
) | rpl::then(
|
|
anim::Disables()
|
|
) | rpl::start_with_next([=](bool hide) {
|
|
if (hide) {
|
|
_blobs->setLevel(0.);
|
|
}
|
|
_blobsHideLastTime = hide ? crl::now() : 0;
|
|
if (!hide && !_animation.animating() && isVisible()) {
|
|
_animation.start();
|
|
}
|
|
}, lifetime());
|
|
|
|
const auto &mainRadiusMin = st::historyRecordMainBlobMinRadius;
|
|
const auto mainRadiusDiff = st::historyRecordMainBlobMaxRadius
|
|
- mainRadiusMin;
|
|
paintRequest(
|
|
) | rpl::start_with_next([=](const QRect &clip) {
|
|
Painter p(this);
|
|
|
|
const auto hideProgress = _blobsHideLastTime
|
|
? 1. - std::clamp(
|
|
((crl::now() - _blobsHideLastTime)
|
|
/ (float64)kBlobsScaleEnterDuration),
|
|
0.,
|
|
1.)
|
|
: 1.;
|
|
const auto showProgress = _showProgress.current();
|
|
const auto complete = (showProgress == 1.);
|
|
|
|
p.translate(_center, _center);
|
|
PainterHighQualityEnabler hq(p);
|
|
const auto brush = QBrush(anim::color(
|
|
st::historyRecordVoiceFgInactive,
|
|
st::historyRecordVoiceFgActive,
|
|
_colorProgress));
|
|
|
|
_blobs->paint(p, brush, showProgress * hideProgress);
|
|
|
|
const auto radius = (mainRadiusMin
|
|
+ (mainRadiusDiff * _blobs->currentLevel())) * showProgress;
|
|
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(brush);
|
|
p.drawEllipse(QPointF(), radius, radius);
|
|
|
|
if (!complete) {
|
|
p.setOpacity(showProgress);
|
|
}
|
|
|
|
// Paint icon.
|
|
{
|
|
const auto stateProgress = _stateChangedAnimation.value(0.);
|
|
const auto scale = (std::cos(M_PI * 2 * stateProgress) + 1.) * .5;
|
|
if (scale < 1.) {
|
|
p.scale(scale, scale);
|
|
}
|
|
const auto state = *currentState;
|
|
const auto icon = (state == Type::Send)
|
|
? st::historySendIcon
|
|
: st::historyRecordVoiceActive;
|
|
const auto position = (state == Type::Send)
|
|
? st::historyRecordSendIconPosition
|
|
: QPoint(0, 0);
|
|
icon.paint(
|
|
p,
|
|
-icon.width() / 2 + position.x(),
|
|
-icon.height() / 2 + position.y(),
|
|
0,
|
|
st::historyRecordVoiceFgActiveIcon->c);
|
|
}
|
|
}, lifetime());
|
|
|
|
_animation.init([=](crl::time now) {
|
|
if (const auto &last = _blobsHideLastTime; (last > 0)
|
|
&& (now - last >= kBlobsScaleEnterDuration)) {
|
|
_animation.stop();
|
|
return false;
|
|
}
|
|
_blobs->updateLevel(now - _lastUpdateTime);
|
|
_lastUpdateTime = now;
|
|
update();
|
|
return true;
|
|
});
|
|
|
|
rpl::merge(
|
|
shownValue(),
|
|
_showProgress.value(
|
|
) | rpl::map(rpl::mappers::_1 != 0.) | rpl::distinct_until_changed()
|
|
) | rpl::start_with_next([=](bool show) {
|
|
setVisible(show);
|
|
setMouseTracking(show);
|
|
if (!show) {
|
|
_animation.stop();
|
|
_showProgress = 0.;
|
|
_blobs->resetLevel();
|
|
_state = Type::Record;
|
|
} else {
|
|
if (!_animation.animating()) {
|
|
_animation.start();
|
|
}
|
|
}
|
|
}, lifetime());
|
|
|
|
actives(
|
|
) | rpl::distinct_until_changed(
|
|
) | rpl::start_with_next([=](bool active) {
|
|
setPointerCursor(active);
|
|
}, lifetime());
|
|
|
|
_state.changes(
|
|
) | rpl::start_with_next([=](Type newState) {
|
|
const auto to = 1.;
|
|
auto callback = [=](float64 value) {
|
|
if (value >= (to * .5)) {
|
|
*currentState = newState;
|
|
}
|
|
update();
|
|
};
|
|
const auto duration = st::historyRecordVoiceDuration * 2;
|
|
_stateChangedAnimation.start(std::move(callback), 0., to, duration);
|
|
}, lifetime());
|
|
}
|
|
|
|
rpl::producer<bool> VoiceRecordButton::actives() const {
|
|
return events(
|
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
|
return (e->type() == QEvent::MouseMove
|
|
|| e->type() == QEvent::Leave
|
|
|| e->type() == QEvent::Enter);
|
|
}) | rpl::map([=](not_null<QEvent*> e) {
|
|
switch(e->type()) {
|
|
case QEvent::MouseMove:
|
|
return inCircle((static_cast<QMouseEvent*>(e.get()))->pos());
|
|
case QEvent::Leave: return false;
|
|
case QEvent::Enter: return inCircle(mapFromGlobal(QCursor::pos()));
|
|
default: return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
rpl::producer<> VoiceRecordButton::clicks() const {
|
|
return Ui::AbstractButton::clicks(
|
|
) | rpl::to_empty | rpl::filter([=] {
|
|
return inCircle(mapFromGlobal(QCursor::pos()));
|
|
});
|
|
}
|
|
|
|
bool VoiceRecordButton::inCircle(const QPoint &localPos) const {
|
|
const auto &radii = st::historyRecordMainBlobMaxRadius;
|
|
const auto dx = std::abs(localPos.x() - _center);
|
|
if (dx > radii) {
|
|
return false;
|
|
}
|
|
const auto dy = std::abs(localPos.y() - _center);
|
|
if (dy > radii) {
|
|
return false;
|
|
} else if (dx + dy <= radii) {
|
|
return true;
|
|
}
|
|
return ((dx * dx + dy * dy) <= (radii * radii));
|
|
}
|
|
|
|
void VoiceRecordButton::requestPaintProgress(float64 progress) {
|
|
_showProgress = progress;
|
|
update();
|
|
}
|
|
|
|
void VoiceRecordButton::requestPaintColor(float64 progress) {
|
|
if (_colorProgress == progress) {
|
|
return;
|
|
}
|
|
_colorProgress = progress;
|
|
update();
|
|
}
|
|
|
|
void VoiceRecordButton::setType(Type state) {
|
|
_state = state;
|
|
}
|
|
|
|
} // namespace HistoryView::Controls
|