mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Replaced record circle button with bezier circle.
This commit is contained in:
parent
cb84e70bdc
commit
7a32d78689
6 changed files with 818 additions and 183 deletions
|
@ -479,6 +479,8 @@ PRIVATE
|
||||||
history/view/controls/history_view_compose_controls.h
|
history/view/controls/history_view_compose_controls.h
|
||||||
history/view/controls/history_view_voice_record_bar.cpp
|
history/view/controls/history_view_voice_record_bar.cpp
|
||||||
history/view/controls/history_view_voice_record_bar.h
|
history/view/controls/history_view_voice_record_bar.h
|
||||||
|
history/view/controls/history_view_voice_record_button.cpp
|
||||||
|
history/view/controls/history_view_voice_record_button.h
|
||||||
history/view/media/history_view_call.h
|
history/view/media/history_view_call.h
|
||||||
history/view/media/history_view_call.cpp
|
history/view/media/history_view_call.cpp
|
||||||
history/view/media/history_view_contact.h
|
history/view/media/history_view_contact.h
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "history/view/controls/history_view_voice_record_button.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
|
@ -59,42 +60,6 @@ enum class FilterType {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class RecordLevel final : public Ui::AbstractButton {
|
|
||||||
public:
|
|
||||||
RecordLevel(
|
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
rpl::producer<> leaveWindowEventProducer);
|
|
||||||
|
|
||||||
void requestPaintColor(float64 progress);
|
|
||||||
void requestPaintProgress(float64 progress);
|
|
||||||
void requestPaintLevel(quint16 level);
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<bool> actives() const;
|
|
||||||
|
|
||||||
[[nodiscard]] bool inCircle(const QPoint &localPos) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void init();
|
|
||||||
|
|
||||||
void drawProgress(Painter &p);
|
|
||||||
|
|
||||||
const int _height;
|
|
||||||
const int _center;
|
|
||||||
|
|
||||||
rpl::variable<float64> _showProgress = 0.;
|
|
||||||
rpl::variable<float64> _colorProgress = 0.;
|
|
||||||
rpl::variable<bool> _inCircle = false;
|
|
||||||
|
|
||||||
bool recordingAnimationCallback(crl::time now);
|
|
||||||
|
|
||||||
// This can animate for a very long time (like in music playing),
|
|
||||||
// so it should be a Basic, not a Simple animation.
|
|
||||||
Ui::Animations::Basic _recordingAnimation;
|
|
||||||
anim::value _recordingLevel;
|
|
||||||
|
|
||||||
rpl::lifetime _showingLifetime;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RecordLock final : public Ui::RpWidget {
|
class RecordLock final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
RecordLock(not_null<Ui::RpWidget*> parent);
|
RecordLock(not_null<Ui::RpWidget*> parent);
|
||||||
|
@ -117,148 +82,6 @@ private:
|
||||||
rpl::variable<float64> _progress = 0.;
|
rpl::variable<float64> _progress = 0.;
|
||||||
};
|
};
|
||||||
|
|
||||||
RecordLevel::RecordLevel(
|
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
rpl::producer<> leaveWindowEventProducer)
|
|
||||||
: AbstractButton(parent)
|
|
||||||
, _height(st::historyRecordLevelMaxRadius * 2)
|
|
||||||
, _center(_height / 2)
|
|
||||||
, _recordingAnimation([=](crl::time now) {
|
|
||||||
return recordingAnimationCallback(now);
|
|
||||||
}) {
|
|
||||||
resize(_height, _height);
|
|
||||||
std::move(
|
|
||||||
leaveWindowEventProducer
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
_inCircle = false;
|
|
||||||
}, lifetime());
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordLevel::requestPaintLevel(quint16 level) {
|
|
||||||
_recordingLevel.start(level);
|
|
||||||
_recordingAnimation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RecordLevel::recordingAnimationCallback(crl::time now) {
|
|
||||||
const auto dt = anim::Disabled()
|
|
||||||
? 1.
|
|
||||||
: ((now - _recordingAnimation.started())
|
|
||||||
/ float64(kRecordingUpdateDelta));
|
|
||||||
if (dt >= 1.) {
|
|
||||||
_recordingLevel.finish();
|
|
||||||
} else {
|
|
||||||
_recordingLevel.update(dt, anim::sineInOut);
|
|
||||||
}
|
|
||||||
if (!anim::Disabled()) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
return (dt < 1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordLevel::init() {
|
|
||||||
const auto hasProgress = [](auto value) { return value != 0.; };
|
|
||||||
|
|
||||||
paintRequest(
|
|
||||||
) | rpl::start_with_next([=](const QRect &clip) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
drawProgress(p);
|
|
||||||
|
|
||||||
p.setOpacity(_showProgress.current());
|
|
||||||
st::historyRecordVoiceActive.paintInCenter(p, rect());
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
rpl::merge(
|
|
||||||
shownValue(),
|
|
||||||
_showProgress.value() | rpl::map(hasProgress)
|
|
||||||
) | rpl::start_with_next([=](bool show) {
|
|
||||||
setVisible(show);
|
|
||||||
setMouseTracking(show);
|
|
||||||
if (!show) {
|
|
||||||
_recordingLevel = anim::value();
|
|
||||||
_recordingAnimation.stop();
|
|
||||||
_showingLifetime.destroy();
|
|
||||||
_showProgress = 0.;
|
|
||||||
}
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
actives(
|
|
||||||
) | rpl::distinct_until_changed(
|
|
||||||
) | rpl::start_with_next([=](bool active) {
|
|
||||||
setPointerCursor(active);
|
|
||||||
}, lifetime());
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<bool> RecordLevel::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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RecordLevel::inCircle(const QPoint &localPos) const {
|
|
||||||
const auto &radii = st::historyRecordLevelMaxRadius;
|
|
||||||
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 RecordLevel::drawProgress(Painter &p) {
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
const auto color = anim::color(
|
|
||||||
st::historyRecordSignalColor,
|
|
||||||
st::historyRecordVoiceFgActive,
|
|
||||||
_colorProgress.current());
|
|
||||||
p.setBrush(color);
|
|
||||||
|
|
||||||
const auto progress = _showProgress.current();
|
|
||||||
|
|
||||||
const auto center = QPoint(_center, _center);
|
|
||||||
const int mainRadii = progress * st::historyRecordLevelMainRadius;
|
|
||||||
|
|
||||||
{
|
|
||||||
p.setOpacity(.5);
|
|
||||||
const auto min = progress * st::historyRecordLevelMinRadius;
|
|
||||||
const auto max = progress * st::historyRecordLevelMaxRadius;
|
|
||||||
const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.);
|
|
||||||
const auto radii = qRound(min + (delta * (max - min)));
|
|
||||||
p.drawEllipse(center, radii, radii);
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.drawEllipse(center, mainRadii, mainRadii);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordLevel::requestPaintProgress(float64 progress) {
|
|
||||||
_showProgress = progress;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordLevel::requestPaintColor(float64 progress) {
|
|
||||||
_colorProgress = progress;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordLock::RecordLock(not_null<Ui::RpWidget*> parent) : RpWidget(parent) {
|
RecordLock::RecordLock(not_null<Ui::RpWidget*> parent) : RpWidget(parent) {
|
||||||
resize(
|
resize(
|
||||||
st::historyRecordLockTopShadow.width(),
|
st::historyRecordLockTopShadow.width(),
|
||||||
|
@ -419,7 +242,7 @@ VoiceRecordBar::VoiceRecordBar(
|
||||||
, _controller(controller)
|
, _controller(controller)
|
||||||
, _send(send)
|
, _send(send)
|
||||||
, _lock(std::make_unique<RecordLock>(sectionWidget))
|
, _lock(std::make_unique<RecordLock>(sectionWidget))
|
||||||
, _level(std::make_unique<RecordLevel>(
|
, _level(std::make_unique<VoiceRecordButton>(
|
||||||
sectionWidget,
|
sectionWidget,
|
||||||
_controller->widget()->leaveEvents()))
|
_controller->widget()->leaveEvents()))
|
||||||
, _startTimer([=] { startRecording(); })
|
, _startTimer([=] { startRecording(); })
|
||||||
|
|
|
@ -23,7 +23,7 @@ class SessionController;
|
||||||
|
|
||||||
namespace HistoryView::Controls {
|
namespace HistoryView::Controls {
|
||||||
|
|
||||||
class RecordLevel;
|
class VoiceRecordButton;
|
||||||
class RecordLock;
|
class RecordLock;
|
||||||
|
|
||||||
class VoiceRecordBar final : public Ui::RpWidget {
|
class VoiceRecordBar final : public Ui::RpWidget {
|
||||||
|
@ -100,7 +100,7 @@ private:
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
const std::shared_ptr<Ui::SendButton> _send;
|
const std::shared_ptr<Ui::SendButton> _send;
|
||||||
const std::unique_ptr<RecordLock> _lock;
|
const std::unique_ptr<RecordLock> _lock;
|
||||||
const std::unique_ptr<RecordLevel> _level;
|
const std::unique_ptr<VoiceRecordButton> _level;
|
||||||
|
|
||||||
base::Timer _startTimer;
|
base::Timer _startTimer;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,746 @@
|
||||||
|
/*
|
||||||
|
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 "styles/style_chat.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
|
#include <QMatrix>
|
||||||
|
|
||||||
|
namespace HistoryView::Controls {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kRecordingUpdateDelta = crl::time(100);
|
||||||
|
|
||||||
|
constexpr auto kSegmentsCount = 12;
|
||||||
|
constexpr auto kMajorDegreeOffset = 360 / kSegmentsCount;
|
||||||
|
constexpr auto kSixtyDegrees = 60;
|
||||||
|
|
||||||
|
constexpr auto kEnterIdleAnimationDuration = crl::time(1200);
|
||||||
|
|
||||||
|
constexpr auto kRotationSpeed = 0.36 * 0.1;
|
||||||
|
|
||||||
|
constexpr auto kRandomAdditionFactor = 0.3;
|
||||||
|
|
||||||
|
constexpr auto kIdleRadiusGlobalFactor = 0.56;
|
||||||
|
constexpr auto kIdleRadiusFactor = 0.15 * 0.5;
|
||||||
|
|
||||||
|
constexpr auto kOpacityMajor = 0.30;
|
||||||
|
constexpr auto kOpacityMinor = 0.15;
|
||||||
|
|
||||||
|
constexpr auto kIdleRotationSpeed = 0.2;
|
||||||
|
constexpr auto kIdleRotateDiff = 0.1 * kIdleRotationSpeed;
|
||||||
|
|
||||||
|
constexpr auto kWaveAngle = 0.03;
|
||||||
|
|
||||||
|
constexpr auto kAnimationSpeedMajor = 1.5;// - 0.65;
|
||||||
|
constexpr auto kAnimationSpeedMinor = 1.5;// - 0.45;
|
||||||
|
constexpr auto kAnimationSpeedCircle = 1.5;// - 0.25;
|
||||||
|
|
||||||
|
constexpr auto kAmplitudeDiffFactorMax = 500.;
|
||||||
|
constexpr auto kAmplitudeDiffFactorMajor = 300.;
|
||||||
|
constexpr auto kAmplitudeDiffFactorMinor = 400.;
|
||||||
|
|
||||||
|
constexpr auto kFlingDistanceFactorMajor = 8 * 16;
|
||||||
|
constexpr auto kFlingDistanceFactorMinor = 20 * 16;
|
||||||
|
|
||||||
|
constexpr auto kFlingInAnimationDurationMajor = 200;
|
||||||
|
constexpr auto kFlingInAnimationDurationMinor = 350;
|
||||||
|
constexpr auto kFlingOutAnimationDurationMajor = 220;
|
||||||
|
constexpr auto kFlingOutAnimationDurationMinor = 380;
|
||||||
|
|
||||||
|
constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2 * 0.5;
|
||||||
|
constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2 * 0.5;
|
||||||
|
|
||||||
|
constexpr auto kSmallWaveRadius = 0.55;
|
||||||
|
|
||||||
|
constexpr auto kFlingDistance = 0.50;
|
||||||
|
|
||||||
|
constexpr auto kMinDivider = 100.;
|
||||||
|
|
||||||
|
constexpr auto kMaxAmplitude = 1800.;
|
||||||
|
|
||||||
|
constexpr auto kZeroPoint = QPointF(0, 0);
|
||||||
|
|
||||||
|
void ApplyTo(float64 &value, const float64 &to, const float64 &diff) {
|
||||||
|
if ((value != to) && ((diff > 0) == (value > to))) {
|
||||||
|
value = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Number>
|
||||||
|
void Normalize(Number &value, Number right) {
|
||||||
|
if (value >= right) {
|
||||||
|
value -= right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float64 RandomAdditional() {
|
||||||
|
return (rand_value<int>() % 100 / 100.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerformAnimation(
|
||||||
|
rpl::producer<crl::time> &&animationTicked,
|
||||||
|
Fn<void(float64)> &&applyValue,
|
||||||
|
Fn<void()> &&finishCallback,
|
||||||
|
float64 duration,
|
||||||
|
float64 from,
|
||||||
|
float64 to,
|
||||||
|
rpl::lifetime &lifetime) {
|
||||||
|
lifetime.destroy();
|
||||||
|
const auto animValue =
|
||||||
|
lifetime.make_state<anim::value>(from, to);
|
||||||
|
const auto animStarted = crl::now();
|
||||||
|
std::move(
|
||||||
|
animationTicked
|
||||||
|
) | rpl::start_with_next([=,
|
||||||
|
applyValue = std::move(applyValue),
|
||||||
|
finishCallback = std::move(finishCallback),
|
||||||
|
&lifetime](crl::time now) mutable {
|
||||||
|
const auto dt = anim::Disabled()
|
||||||
|
? 1.
|
||||||
|
: ((now - animStarted) / duration);
|
||||||
|
if (dt >= 1.) {
|
||||||
|
animValue->finish();
|
||||||
|
applyValue(animValue->current());
|
||||||
|
lifetime.destroy();
|
||||||
|
if (finishCallback) {
|
||||||
|
finishCallback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
animValue->update(dt, anim::linear);
|
||||||
|
applyValue(animValue->current());
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class CircleBezier final {
|
||||||
|
public:
|
||||||
|
CircleBezier(int n);
|
||||||
|
|
||||||
|
void computeRandomAdditionals();
|
||||||
|
void paintCircle(
|
||||||
|
Painter &p,
|
||||||
|
const QColor &c,
|
||||||
|
float64 radius,
|
||||||
|
float64 cubicBezierFactor,
|
||||||
|
float64 idleStateDiff,
|
||||||
|
float64 radiusDiff,
|
||||||
|
float64 randomFactor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Points {
|
||||||
|
QPointF point;
|
||||||
|
QPointF control;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int _segmentsCount;
|
||||||
|
const float64 _segmentLength;
|
||||||
|
std::vector<float64> _randomAdditionals;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Wave final {
|
||||||
|
public:
|
||||||
|
Wave(
|
||||||
|
rpl::producer<crl::time> animationTicked,
|
||||||
|
int n,
|
||||||
|
float64 rotationOffset,
|
||||||
|
float64 amplitudeRadius,
|
||||||
|
float64 amplitudeWaveDiff,
|
||||||
|
float64 fling,
|
||||||
|
int flingDistanceFactor,
|
||||||
|
int flingInAnimationDuration,
|
||||||
|
int flingOutAnimationDuration,
|
||||||
|
float64 amplitudeDiffSpeed,
|
||||||
|
float64 amplitudeDiffFactor,
|
||||||
|
bool isDirectionClockwise);
|
||||||
|
|
||||||
|
void setValue(float64 value);
|
||||||
|
void tick(float64 circleRadius, crl::time lastUpdateTime);
|
||||||
|
|
||||||
|
void paint(Painter &p, QColor c);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void initEnterIdleAnimation(rpl::producer<crl::time> animationTicked);
|
||||||
|
void initFlingAnimation(rpl::producer<crl::time> animationTicked);
|
||||||
|
|
||||||
|
Ui::Animations::Simple _flingAnimation;
|
||||||
|
|
||||||
|
const std::unique_ptr<CircleBezier> _circleBezier;
|
||||||
|
|
||||||
|
const float _rotationOffset;
|
||||||
|
const float64 _idleGlobalRadius;
|
||||||
|
const float64 _amplitudeRadius;
|
||||||
|
const float64 _amplitudeWaveDiff;
|
||||||
|
const float64 _randomAdditions;
|
||||||
|
const float64 _fling;
|
||||||
|
const int _flingDistanceFactor;
|
||||||
|
const int _flingInAnimationDuration;
|
||||||
|
const int _flingOutAnimationDuration;
|
||||||
|
const float64 _amplitudeDiffSpeed;
|
||||||
|
const float64 _amplitudeDiffFactor;
|
||||||
|
const int _directionClockwise;
|
||||||
|
|
||||||
|
bool _incRandomAdditionals = false;
|
||||||
|
bool _isIdle = true;
|
||||||
|
bool _wasFling = false;
|
||||||
|
float64 _amplitude = 0.;
|
||||||
|
float64 _animateAmplitudeDiff = 0.;
|
||||||
|
float64 _animateAmplitudeSlowDiff = 0.;
|
||||||
|
float64 _animateToAmplitude = 0.;
|
||||||
|
float64 _flingRadius = 0.;
|
||||||
|
float64 _idleRadius = 0.;
|
||||||
|
float64 _idleRotation = 0.;
|
||||||
|
float64 _lastRadius = 0.;
|
||||||
|
float64 _rotation = 0.;
|
||||||
|
float64 _sineAngleMax = 0.;
|
||||||
|
float64 _slowAmplitude = 0.;
|
||||||
|
float64 _waveAngle = 0.;
|
||||||
|
float64 _waveDiff = 0.;
|
||||||
|
|
||||||
|
rpl::event_stream<float64> _flingAnimationRequests;
|
||||||
|
rpl::event_stream<> _enterIdleAnimationRequests;
|
||||||
|
rpl::lifetime _animationEnterIdleLifetime;
|
||||||
|
rpl::lifetime _animationFlingLifetime;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecordCircle final {
|
||||||
|
public:
|
||||||
|
RecordCircle(rpl::producer<crl::time> animationTicked);
|
||||||
|
|
||||||
|
void setAmplitude(float64 value);
|
||||||
|
void paint(Painter &p, QColor c);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const std::unique_ptr<Wave> _majorWave;
|
||||||
|
const std::unique_ptr<Wave> _minorWave;
|
||||||
|
|
||||||
|
float64 _amplitude = 0.;
|
||||||
|
float64 _animateToAmplitude = 0.;
|
||||||
|
float64 _animateAmplitudeDiff = 0.;
|
||||||
|
crl::time _lastUpdateTime = 0;
|
||||||
|
|
||||||
|
rpl::lifetime _animationLifetime;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
CircleBezier::CircleBezier(int n)
|
||||||
|
: _segmentsCount(n)
|
||||||
|
, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n)))
|
||||||
|
, _randomAdditionals(n) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircleBezier::computeRandomAdditionals() {
|
||||||
|
ranges::generate(_randomAdditionals, RandomAdditional);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircleBezier::paintCircle(
|
||||||
|
Painter &p,
|
||||||
|
const QColor &c,
|
||||||
|
float64 radius,
|
||||||
|
float64 cubicBezierFactor,
|
||||||
|
float64 idleStateDiff,
|
||||||
|
float64 radiusDiff,
|
||||||
|
float64 randomFactor) {
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
const auto r1 = radius - idleStateDiff / 2. - radiusDiff / 2.;
|
||||||
|
const auto r2 = radius + radiusDiff / 2. + idleStateDiff / 2.;
|
||||||
|
const auto l = _segmentLength * std::max(r1, r2) * cubicBezierFactor;
|
||||||
|
|
||||||
|
auto m = QMatrix();
|
||||||
|
|
||||||
|
const auto preparePoints = [&](int i, bool isStart) -> Points {
|
||||||
|
Normalize(i, _segmentsCount);
|
||||||
|
const auto randomAddition = randomFactor * _randomAdditionals[i];
|
||||||
|
const auto r = ((i % 2 == 0) ? r1 : r2) + randomAddition;
|
||||||
|
|
||||||
|
m.reset();
|
||||||
|
m.rotate(360. / _segmentsCount * i);
|
||||||
|
const auto sign = isStart ? 1 : -1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
(isStart && i) ? QPointF() : m.map(QPointF(0, -r)),
|
||||||
|
m.map(QPointF(sign * (l + randomAddition * _segmentLength), -r)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto &[startPoint, _] = preparePoints(0, true);
|
||||||
|
|
||||||
|
auto path = QPainterPath();
|
||||||
|
path.moveTo(startPoint);
|
||||||
|
|
||||||
|
for (auto i = 0; i < _segmentsCount; i++) {
|
||||||
|
const auto &[_, startControl] = preparePoints(i, true);
|
||||||
|
const auto &[end, endControl] = preparePoints(i + 1, false);
|
||||||
|
|
||||||
|
path.cubicTo(startControl, endControl, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
|
||||||
|
auto pen = QPen(Qt::NoPen);
|
||||||
|
pen.setCapStyle(Qt::RoundCap);
|
||||||
|
pen.setJoinStyle(Qt::RoundJoin);
|
||||||
|
|
||||||
|
p.setPen(pen);
|
||||||
|
p.fillPath(path, c);
|
||||||
|
p.drawPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Wave::Wave(
|
||||||
|
rpl::producer<crl::time> animationTicked,
|
||||||
|
int n,
|
||||||
|
float64 rotationOffset,
|
||||||
|
float64 amplitudeRadius,
|
||||||
|
float64 amplitudeWaveDiff,
|
||||||
|
float64 fling,
|
||||||
|
int flingDistanceFactor,
|
||||||
|
int flingInAnimationDuration,
|
||||||
|
int flingOutAnimationDuration,
|
||||||
|
float64 amplitudeDiffSpeed,
|
||||||
|
float64 amplitudeDiffFactor,
|
||||||
|
bool isDirectionClockwise)
|
||||||
|
: _circleBezier(std::make_unique<CircleBezier>(n))
|
||||||
|
, _rotationOffset(rotationOffset)
|
||||||
|
, _idleGlobalRadius(st::historyRecordRadiusDiffMin * kIdleRadiusGlobalFactor)
|
||||||
|
, _amplitudeRadius(amplitudeRadius)
|
||||||
|
, _amplitudeWaveDiff(amplitudeWaveDiff)
|
||||||
|
, _randomAdditions(st::historyRecordRandomAddition * kRandomAdditionFactor)
|
||||||
|
, _fling(fling)
|
||||||
|
, _flingDistanceFactor(flingDistanceFactor)
|
||||||
|
, _flingInAnimationDuration(flingInAnimationDuration)
|
||||||
|
, _flingOutAnimationDuration(flingOutAnimationDuration)
|
||||||
|
, _amplitudeDiffSpeed(amplitudeDiffSpeed)
|
||||||
|
, _amplitudeDiffFactor(amplitudeDiffFactor)
|
||||||
|
, _directionClockwise(isDirectionClockwise ? 1 : -1)
|
||||||
|
, _rotation(rotationOffset) {
|
||||||
|
initEnterIdleAnimation(rpl::duplicate(animationTicked));
|
||||||
|
initFlingAnimation(std::move(animationTicked));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wave::setValue(float64 value) {
|
||||||
|
_animateToAmplitude = value;
|
||||||
|
|
||||||
|
const auto amplitudeDelta = (_animateToAmplitude - _amplitude);
|
||||||
|
const auto amplitudeSlowDelta = (_animateToAmplitude - _slowAmplitude);
|
||||||
|
const auto factor = (_animateToAmplitude <= _amplitude)
|
||||||
|
? kAmplitudeDiffFactorMax
|
||||||
|
: _amplitudeDiffFactor;
|
||||||
|
_animateAmplitudeDiff = amplitudeDelta
|
||||||
|
/ (kMinDivider + factor * _amplitudeDiffSpeed);
|
||||||
|
_animateAmplitudeSlowDiff = amplitudeSlowDelta
|
||||||
|
/ (kMinDivider + kAmplitudeDiffFactorMax * _amplitudeDiffSpeed);
|
||||||
|
|
||||||
|
const auto idle = value < 0.1;
|
||||||
|
if (_isIdle != idle && idle) {
|
||||||
|
_enterIdleAnimationRequests.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
_isIdle = idle;
|
||||||
|
|
||||||
|
if (!_isIdle) {
|
||||||
|
_animationEnterIdleLifetime.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wave::initEnterIdleAnimation(rpl::producer<crl::time> animationTicked) {
|
||||||
|
_enterIdleAnimationRequests.events(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
const auto &k = kSixtyDegrees;
|
||||||
|
|
||||||
|
const auto rotation = _rotation;
|
||||||
|
const auto rotationTo = std::round(rotation / k) * k
|
||||||
|
+ _rotationOffset;
|
||||||
|
const auto waveDiff = _waveDiff;
|
||||||
|
|
||||||
|
auto applyValue = [=](float64 v) {
|
||||||
|
_rotation = rotationTo + (rotation - rotationTo) * v;
|
||||||
|
_waveDiff = 1. + (waveDiff - 1.) * v;
|
||||||
|
_waveAngle = std::acos(_waveDiff * _directionClockwise);
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformAnimation(
|
||||||
|
rpl::duplicate(animationTicked),
|
||||||
|
std::move(applyValue),
|
||||||
|
nullptr,
|
||||||
|
kEnterIdleAnimationDuration,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
_animationEnterIdleLifetime);
|
||||||
|
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wave::initFlingAnimation(rpl::producer<crl::time> animationTicked) {
|
||||||
|
_flingAnimationRequests.events(
|
||||||
|
) | rpl::start_with_next([=](float64 delta) {
|
||||||
|
|
||||||
|
const auto fling = _fling * 2;
|
||||||
|
const auto flingDistance = delta
|
||||||
|
* _amplitudeRadius
|
||||||
|
* _flingDistanceFactor
|
||||||
|
* fling;
|
||||||
|
|
||||||
|
const auto applyValue = [=](float64 v) {
|
||||||
|
_flingRadius = v;
|
||||||
|
};
|
||||||
|
auto finishCallback = [=] {
|
||||||
|
PerformAnimation(
|
||||||
|
rpl::duplicate(animationTicked),
|
||||||
|
applyValue,
|
||||||
|
nullptr,
|
||||||
|
_flingOutAnimationDuration * fling,
|
||||||
|
flingDistance,
|
||||||
|
0,
|
||||||
|
_animationFlingLifetime);
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformAnimation(
|
||||||
|
rpl::duplicate(animationTicked),
|
||||||
|
applyValue,
|
||||||
|
std::move(finishCallback),
|
||||||
|
_flingInAnimationDuration * fling,
|
||||||
|
_flingRadius,
|
||||||
|
flingDistance,
|
||||||
|
_animationFlingLifetime);
|
||||||
|
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) {
|
||||||
|
const auto dt = (crl::now() - lastUpdateTime);
|
||||||
|
|
||||||
|
if (_animateToAmplitude != _amplitude) {
|
||||||
|
_amplitude += _animateAmplitudeDiff * dt;
|
||||||
|
ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff);
|
||||||
|
|
||||||
|
if (std::abs(_amplitude - _animateToAmplitude) * _amplitudeRadius
|
||||||
|
< (st::historyRecordRandomAddition / 2)) {
|
||||||
|
if (!_wasFling) {
|
||||||
|
_flingAnimationRequests.fire_copy(_animateAmplitudeDiff);
|
||||||
|
_wasFling = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_wasFling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_animateToAmplitude != _slowAmplitude) {
|
||||||
|
_slowAmplitude += _animateAmplitudeSlowDiff * dt;
|
||||||
|
if (std::abs(_slowAmplitude - _amplitude) > 0.2) {
|
||||||
|
_slowAmplitude = _amplitude + (_slowAmplitude > _amplitude ?
|
||||||
|
0.2 : -0.2);
|
||||||
|
}
|
||||||
|
ApplyTo(_slowAmplitude,
|
||||||
|
_animateToAmplitude,
|
||||||
|
_animateAmplitudeSlowDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
_idleRadius = circleRadius * kIdleRadiusFactor;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto delta = (_sineAngleMax - _animateToAmplitude);
|
||||||
|
if (std::abs(delta) - 0.25 < 0) {
|
||||||
|
_sineAngleMax = _animateToAmplitude;
|
||||||
|
} else {
|
||||||
|
_sineAngleMax -= 0.25 * ((delta < 0) ? -1 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isIdle) {
|
||||||
|
_rotation += dt
|
||||||
|
* (kRotationSpeed * 4. * (_amplitude > 0.5 ? 1 : _amplitude / 0.5)
|
||||||
|
+ kRotationSpeed * 0.5);
|
||||||
|
Normalize(_rotation, 360.);
|
||||||
|
} else {
|
||||||
|
_idleRotation += kIdleRotateDiff * dt;
|
||||||
|
Normalize(_idleRotation, 360.);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastRadius = circleRadius;
|
||||||
|
|
||||||
|
if (!_isIdle) {
|
||||||
|
_waveAngle += (_amplitudeWaveDiff * _sineAngleMax) * dt;
|
||||||
|
_waveDiff = std::cos(_waveAngle) * _directionClockwise;
|
||||||
|
|
||||||
|
if ((_waveDiff != 0) && ((_waveDiff > 0) == _incRandomAdditionals)) {
|
||||||
|
_circleBezier->computeRandomAdditionals();
|
||||||
|
_incRandomAdditionals = !_incRandomAdditionals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Wave::paint(Painter &p, QColor c) {
|
||||||
|
const auto waveAmplitude = _amplitude < 0.3 ? _amplitude / 0.3 : 1.;
|
||||||
|
const auto radiusDiff = st::historyRecordRadiusDiffMin
|
||||||
|
+ st::historyRecordRadiusDiff * kWaveAngle * _animateToAmplitude;
|
||||||
|
|
||||||
|
const auto diffFactor = 0.35 * waveAmplitude * _waveDiff;
|
||||||
|
|
||||||
|
const auto radius = (_lastRadius + _amplitudeRadius * _amplitude)
|
||||||
|
+ _idleGlobalRadius
|
||||||
|
+ (_flingRadius * waveAmplitude);
|
||||||
|
|
||||||
|
const auto cubicBezierFactor = 1.
|
||||||
|
+ std::abs(diffFactor) * waveAmplitude
|
||||||
|
+ (1. - waveAmplitude) * kIdleRadiusFactor;
|
||||||
|
|
||||||
|
const auto circleRadiusDiff = std::max(
|
||||||
|
radiusDiff * diffFactor,
|
||||||
|
st::historyRecordLevelMainRadius - radius);
|
||||||
|
|
||||||
|
p.rotate((_rotation + _idleRotation) * _directionClockwise);
|
||||||
|
|
||||||
|
_circleBezier->paintCircle(
|
||||||
|
p,
|
||||||
|
c,
|
||||||
|
radius,
|
||||||
|
cubicBezierFactor,
|
||||||
|
_idleRadius * (1. - waveAmplitude),
|
||||||
|
circleRadiusDiff,
|
||||||
|
waveAmplitude * _waveDiff * _randomAdditions);
|
||||||
|
|
||||||
|
p.rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordCircle::RecordCircle(rpl::producer<crl::time> animationTicked)
|
||||||
|
: _majorWave(std::make_unique<Wave>(
|
||||||
|
rpl::duplicate(animationTicked),
|
||||||
|
kSegmentsCount,
|
||||||
|
kMajorDegreeOffset,
|
||||||
|
st::historyRecordMajorAmplitudeRadius,
|
||||||
|
kSineWaveSpeedMajor,
|
||||||
|
0.,
|
||||||
|
kFlingDistanceFactorMajor,
|
||||||
|
kFlingInAnimationDurationMajor,
|
||||||
|
kFlingOutAnimationDurationMajor,
|
||||||
|
kAnimationSpeedMajor,
|
||||||
|
kAmplitudeDiffFactorMajor,
|
||||||
|
true))
|
||||||
|
, _minorWave(std::make_unique<Wave>(
|
||||||
|
std::move(animationTicked),
|
||||||
|
kSegmentsCount,
|
||||||
|
0,
|
||||||
|
st::historyRecordMinorAmplitudeRadius
|
||||||
|
+ st::historyRecordMinorAmplitudeRadius * kSmallWaveRadius,
|
||||||
|
kSineWaveSpeedMinor,
|
||||||
|
kFlingDistance,
|
||||||
|
kFlingDistanceFactorMinor,
|
||||||
|
kFlingInAnimationDurationMinor,
|
||||||
|
kFlingOutAnimationDurationMinor,
|
||||||
|
kAnimationSpeedMinor,
|
||||||
|
kAmplitudeDiffFactorMinor,
|
||||||
|
false)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordCircle::setAmplitude(float64 value) {
|
||||||
|
_animateToAmplitude = std::min(kMaxAmplitude, value) / kMaxAmplitude;
|
||||||
|
_majorWave->setValue(_animateToAmplitude);
|
||||||
|
_minorWave->setValue(_animateToAmplitude);
|
||||||
|
_animateAmplitudeDiff = (_animateToAmplitude - _amplitude)
|
||||||
|
/ (kMinDivider + kAmplitudeDiffFactorMax * kAnimationSpeedCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordCircle::paint(Painter &p, QColor c) {
|
||||||
|
|
||||||
|
const auto dt = (crl::now() - _lastUpdateTime);
|
||||||
|
if (_animateToAmplitude != _amplitude) {
|
||||||
|
_amplitude += _animateAmplitudeDiff * dt;
|
||||||
|
ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto radius = (st::historyRecordLevelMainRadius
|
||||||
|
+ st::historyRecordLevelMainRadiusAmplitude * _amplitude);
|
||||||
|
|
||||||
|
_majorWave->tick(radius, _lastUpdateTime);
|
||||||
|
_minorWave->tick(radius, _lastUpdateTime);
|
||||||
|
_lastUpdateTime = crl::now();
|
||||||
|
|
||||||
|
const auto opacity = p.opacity();
|
||||||
|
p.setOpacity(kOpacityMajor);
|
||||||
|
_majorWave->paint(p, c);
|
||||||
|
p.setOpacity(kOpacityMinor);
|
||||||
|
_minorWave->paint(p, c);
|
||||||
|
p.setOpacity(opacity);
|
||||||
|
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(c);
|
||||||
|
p.drawEllipse(kZeroPoint, radius, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoiceRecordButton::VoiceRecordButton(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
rpl::producer<> leaveWindowEventProducer)
|
||||||
|
: AbstractButton(parent)
|
||||||
|
, _recordCircle(std::make_unique<RecordCircle>(
|
||||||
|
_recordAnimationTicked.events()))
|
||||||
|
, _height(st::historyRecordLevelMaxRadius * 2)
|
||||||
|
, _center(_height / 2)
|
||||||
|
, _recordingAnimation([=](crl::time now) {
|
||||||
|
update();
|
||||||
|
_recordAnimationTicked.fire_copy(now);
|
||||||
|
return true;
|
||||||
|
}) {
|
||||||
|
resize(_height, _height);
|
||||||
|
std::move(
|
||||||
|
leaveWindowEventProducer
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_inCircle = false;
|
||||||
|
}, lifetime());
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
VoiceRecordButton::~VoiceRecordButton() = default;
|
||||||
|
|
||||||
|
void VoiceRecordButton::requestPaintLevel(quint16 level) {
|
||||||
|
_recordCircle->setAmplitude(level);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoiceRecordButton::recordingAnimationCallback(crl::time now) {
|
||||||
|
const auto dt = anim::Disabled()
|
||||||
|
? 1.
|
||||||
|
: ((now - _recordingAnimation.started())
|
||||||
|
/ float64(kRecordingUpdateDelta));
|
||||||
|
if (dt >= 1.) {
|
||||||
|
_recordingLevel.finish();
|
||||||
|
} else {
|
||||||
|
_recordingLevel.update(dt, anim::sineInOut);
|
||||||
|
}
|
||||||
|
if (!anim::Disabled()) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
return (dt < 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceRecordButton::init() {
|
||||||
|
const auto hasProgress = [](auto value) { return value != 0.; };
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=](const QRect &clip) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
p.translate(_center, _center);
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
const auto color = anim::color(
|
||||||
|
st::historyRecordSignalColor,
|
||||||
|
st::historyRecordVoiceFgActive,
|
||||||
|
_colorProgress.current());
|
||||||
|
_recordCircle->paint(p, color);
|
||||||
|
p.resetTransform();
|
||||||
|
|
||||||
|
p.setOpacity(_showProgress.current());
|
||||||
|
st::historyRecordVoiceActive.paintInCenter(p, rect());
|
||||||
|
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
rpl::merge(
|
||||||
|
shownValue(),
|
||||||
|
_showProgress.value() | rpl::map(hasProgress)
|
||||||
|
) | rpl::start_with_next([=](bool show) {
|
||||||
|
setVisible(show);
|
||||||
|
setMouseTracking(show);
|
||||||
|
if (!show) {
|
||||||
|
_recordingLevel = anim::value();
|
||||||
|
_recordingAnimation.stop();
|
||||||
|
_showingLifetime.destroy();
|
||||||
|
_showProgress = 0.;
|
||||||
|
} else {
|
||||||
|
if (!_recordingAnimation.animating()) {
|
||||||
|
_recordingAnimation.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
actives(
|
||||||
|
) | rpl::distinct_until_changed(
|
||||||
|
) | rpl::start_with_next([=](bool active) {
|
||||||
|
setPointerCursor(active);
|
||||||
|
}, 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoiceRecordButton::inCircle(const QPoint &localPos) const {
|
||||||
|
const auto &radii = st::historyRecordLevelMaxRadius;
|
||||||
|
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::drawProgress(Painter &p) {
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
const auto color = anim::color(
|
||||||
|
st::historyRecordSignalColor,
|
||||||
|
st::historyRecordVoiceFgActive,
|
||||||
|
_colorProgress.current());
|
||||||
|
p.setBrush(color);
|
||||||
|
|
||||||
|
const auto progress = _showProgress.current();
|
||||||
|
|
||||||
|
const auto center = QPoint(_center, _center);
|
||||||
|
const int mainRadii = progress * st::historyRecordLevelMainRadius;
|
||||||
|
|
||||||
|
{
|
||||||
|
p.setOpacity(.5);
|
||||||
|
const auto min = progress * st::historyRecordLevelMinRadius;
|
||||||
|
const auto max = progress * st::historyRecordLevelMaxRadius;
|
||||||
|
const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.);
|
||||||
|
const auto radii = qRound(min + (delta * (max - min)));
|
||||||
|
p.drawEllipse(center, radii, radii);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.drawEllipse(center, mainRadii, mainRadii);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceRecordButton::requestPaintProgress(float64 progress) {
|
||||||
|
_showProgress = progress;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceRecordButton::requestPaintColor(float64 progress) {
|
||||||
|
_colorProgress = progress;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView::Controls
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/abstract_button.h"
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
|
namespace HistoryView::Controls {
|
||||||
|
|
||||||
|
class RecordCircle;
|
||||||
|
|
||||||
|
class VoiceRecordButton final : public Ui::AbstractButton {
|
||||||
|
public:
|
||||||
|
VoiceRecordButton(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
rpl::producer<> leaveWindowEventProducer);
|
||||||
|
~VoiceRecordButton();
|
||||||
|
|
||||||
|
void requestPaintColor(float64 progress);
|
||||||
|
void requestPaintProgress(float64 progress);
|
||||||
|
void requestPaintLevel(quint16 level);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<bool> actives() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool inCircle(const QPoint &localPos) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void drawProgress(Painter &p);
|
||||||
|
|
||||||
|
rpl::event_stream<crl::time> _recordAnimationTicked;
|
||||||
|
std::unique_ptr<RecordCircle> _recordCircle;
|
||||||
|
|
||||||
|
const int _height;
|
||||||
|
const int _center;
|
||||||
|
|
||||||
|
rpl::variable<float64> _showProgress = 0.;
|
||||||
|
rpl::variable<float64> _colorProgress = 0.;
|
||||||
|
rpl::variable<bool> _inCircle = false;
|
||||||
|
|
||||||
|
bool recordingAnimationCallback(crl::time now);
|
||||||
|
|
||||||
|
// This can animate for a very long time (like in music playing),
|
||||||
|
// so it should be a Basic, not a Simple animation.
|
||||||
|
Ui::Animations::Basic _recordingAnimation;
|
||||||
|
anim::value _recordingLevel;
|
||||||
|
|
||||||
|
rpl::lifetime _showingLifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView::Controls
|
|
@ -342,9 +342,15 @@ historyRecordFont: font(13px);
|
||||||
historyRecordDurationSkip: 12px;
|
historyRecordDurationSkip: 12px;
|
||||||
historyRecordDurationFg: historyComposeAreaFg;
|
historyRecordDurationFg: historyComposeAreaFg;
|
||||||
|
|
||||||
historyRecordLevelMainRadius: 37px;
|
historyRecordLevelMainRadius: 23px;
|
||||||
|
historyRecordLevelMainRadiusAmplitude: 14px;
|
||||||
|
historyRecordMajorAmplitudeRadius: 14px;
|
||||||
|
historyRecordMinorAmplitudeRadius: 7px;
|
||||||
|
historyRecordRandomAddition: 8px;
|
||||||
|
historyRecordRadiusDiff: 50px;
|
||||||
|
historyRecordRadiusDiffMin: 10px;
|
||||||
historyRecordLevelMinRadius: 38px;
|
historyRecordLevelMinRadius: 38px;
|
||||||
historyRecordLevelMaxRadius: 60px;
|
historyRecordLevelMaxRadius: 70px;
|
||||||
|
|
||||||
historyRecordTextStyle: TextStyle(defaultTextStyle) {
|
historyRecordTextStyle: TextStyle(defaultTextStyle) {
|
||||||
font: historyRecordFont;
|
font: historyRecordFont;
|
||||||
|
|
Loading…
Add table
Reference in a new issue