Added new send recorded voice button with recording animation.

This commit is contained in:
23rd 2020-10-10 21:27:01 +03:00 committed by John Preston
parent 4970740739
commit ba3862e70f
6 changed files with 213 additions and 19 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -59,6 +59,43 @@ 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);
void reset();
[[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);
@ -79,6 +116,149 @@ 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() {
shownValue(
) | rpl::start_with_next([=](bool shown) {
}, lifetime());
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
Painter p(this);
drawProgress(p);
st::historyRecordVoiceActive.paintInCenter(p, rect());
}, lifetime());
_showProgress.changes(
) | rpl::map([](auto value) {
return value != 0.;
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool show) {
setVisible(show);
setMouseTracking(show);
if (!show) {
_recordingLevel = anim::value();
_recordingAnimation.stop();
_showingLifetime.destroy();
}
}, 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(),
@ -222,6 +402,9 @@ VoiceRecordBar::VoiceRecordBar(
, _controller(controller) , _controller(controller)
, _send(send) , _send(send)
, _lock(std::make_unique<RecordLock>(parent)) , _lock(std::make_unique<RecordLock>(parent))
, _level(std::make_unique<RecordLevel>(
parent,
_controller->widget()->leaveEvents()))
, _cancelFont(st::historyRecordFont) , _cancelFont(st::historyRecordFont)
, _recordingAnimation([=](crl::time now) { , _recordingAnimation([=](crl::time now) {
return recordingAnimationCallback(now); return recordingAnimationCallback(now);
@ -264,6 +447,11 @@ void VoiceRecordBar::updateLockGeometry() {
_lock->moveToRight(right, _lock->y()); _lock->moveToRight(right, _lock->y());
} }
void VoiceRecordBar::updateLevelGeometry() {
const auto center = (_send->width() - _level->width()) / 2;
_level->moveToRight(st::historySendRight + center, y() + center);
}
void VoiceRecordBar::init() { void VoiceRecordBar::init() {
hide(); hide();
// Keep VoiceRecordBar behind SendButton. // Keep VoiceRecordBar behind SendButton.
@ -275,6 +463,7 @@ void VoiceRecordBar::init() {
}) | rpl::to_empty }) | rpl::to_empty
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
stackUnder(_send.get()); stackUnder(_send.get());
_level->raise();
}, lifetime()); }, lifetime());
sizeValue( sizeValue(
@ -298,6 +487,7 @@ void VoiceRecordBar::init() {
} }
updateMessageGeometry(); updateMessageGeometry();
updateLockGeometry(); updateLockGeometry();
updateLevelGeometry();
}, lifetime()); }, lifetime());
paintRequest( paintRequest(
@ -347,27 +537,17 @@ void VoiceRecordBar::init() {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
installClickOutsideFilter(); installClickOutsideFilter();
_send->clicks( _level->clicks(
) | rpl::filter([=] { ) | rpl::start_with_next([=] {
return _send->type() == Ui::SendButton::Type::Record;
}) | rpl::start_with_next([=] {
stop(true); stop(true);
}, _recordingLifetime); }, _recordingLifetime);
auto hover = _send->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return e->type() == QEvent::Enter
|| e->type() == QEvent::Leave;
}) | rpl::map([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter);
});
_send->setLockRecord(true); _send->setLockRecord(true);
_send->setForceRippled(true); _send->setForceRippled(true);
rpl::single( rpl::single(
false false
) | rpl::then( ) | rpl::then(
std::move(hover) _level->actives()
) | rpl::start_with_next([=](bool enter) { ) | rpl::start_with_next([=](bool enter) {
_inField = enter; _inField = enter;
}, _recordingLifetime); }, _recordingLifetime);
@ -398,7 +578,7 @@ void VoiceRecordBar::activeAnimate(bool active) {
} else { } else {
auto callback = [=] { auto callback = [=] {
update(_messageRect); update(_messageRect);
_send->requestPaintRecord(activeAnimationRatio()); _level->requestPaintColor(activeAnimationRatio());
}; };
const auto from = active ? 0. : 1.; const auto from = active ? 0. : 1.;
_activeAnimation.start(std::move(callback), from, to, duration); _activeAnimation.start(std::move(callback), from, to, duration);
@ -410,6 +590,7 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
const auto from = show ? 0. : 1.; const auto from = show ? 0. : 1.;
const auto duration = st::historyRecordVoiceShowDuration; const auto duration = st::historyRecordVoiceShowDuration;
auto animationCallback = [=, callback = std::move(callback)](auto value) { auto animationCallback = [=, callback = std::move(callback)](auto value) {
_level->requestPaintProgress(value);
update(); update();
if ((show && value == 1.) || (!show && value == 0.)) { if ((show && value == 1.) || (!show && value == 0.)) {
if (callback) { if (callback) {
@ -429,6 +610,7 @@ void VoiceRecordBar::setLockBottom(rpl::producer<int> &&bottom) {
bottom bottom
) | rpl::start_with_next([=](int value) { ) | rpl::start_with_next([=](int value) {
_lock->moveToLeft(_lock->x(), value - _lock->height()); _lock->moveToLeft(_lock->x(), value - _lock->height());
updateLevelGeometry();
}, lifetime()); }, lifetime());
} }
@ -474,8 +656,12 @@ void VoiceRecordBar::startRecording() {
const auto type = e->type(); const auto type = e->type();
if (type == QEvent::MouseMove) { if (type == QEvent::MouseMove) {
const auto mouse = static_cast<QMouseEvent*>(e.get()); const auto mouse = static_cast<QMouseEvent*>(e.get());
const auto localPos = mapFromGlobal(mouse->globalPos()); const auto globalPos = mouse->globalPos();
_inField = rect().contains(localPos); const auto localPos = mapFromGlobal(globalPos);
const auto inField = rect().contains(localPos);
_inField = inField
? inField
: _level->inCircle(_level->mapFromGlobal(globalPos));
if (_showLockAnimation.animating()) { if (_showLockAnimation.animating()) {
return; return;
@ -504,6 +690,7 @@ bool VoiceRecordBar::recordingAnimationCallback(crl::time now) {
} }
void VoiceRecordBar::recordUpdated(quint16 level, int samples) { void VoiceRecordBar::recordUpdated(quint16 level, int samples) {
_level->requestPaintLevel(level);
_recordingLevel.start(level); _recordingLevel.start(level);
_recordingAnimation.start(); _recordingAnimation.start();
_recordingSamples = samples; _recordingSamples = samples;
@ -698,7 +885,7 @@ void VoiceRecordBar::installClickOutsideFilter() {
} else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) {
return Type::ShowBox; return Type::ShowBox;
} else if (type == QEvent::MouseButtonPress) { } else if (type == QEvent::MouseButtonPress) {
return (noBox && !_send->underMouse()) return (noBox && !_inField.current())
? Type::ShowBox ? Type::ShowBox
: Type::Continue; : Type::Continue;
} }

View file

@ -22,6 +22,7 @@ class SessionController;
namespace HistoryView::Controls { namespace HistoryView::Controls {
class RecordLevel;
class RecordLock; class RecordLock;
class VoiceRecordBar final : public Ui::RpWidget { class VoiceRecordBar final : public Ui::RpWidget {
@ -56,6 +57,7 @@ private:
void updateMessageGeometry(); void updateMessageGeometry();
void updateLockGeometry(); void updateLockGeometry();
void updateLevelGeometry();
void recordError(); void recordError();
void recordUpdated(quint16 level, int samples); void recordUpdated(quint16 level, int samples);
@ -86,6 +88,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;
rpl::event_stream<SendActionUpdate> _sendActionUpdates; rpl::event_stream<SendActionUpdate> _sendActionUpdates;
rpl::event_stream<VoiceToSend> _sendVoiceRequests; rpl::event_stream<VoiceToSend> _sendVoiceRequests;

View file

@ -332,8 +332,8 @@ historyRecordVoiceShowDuration: 120;
historyRecordVoiceDuration: 120; historyRecordVoiceDuration: 120;
historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }};
historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }};
historyRecordVoiceActive: icon {{ "send_control_record", historyRecordVoiceFgActive }}; historyRecordVoiceActive: icon {{ "send_control_record_active", recordActiveIcon }};
historyRecordVoiceCancel: icon {{ "send_control_record", attentionButtonFg }}; historyRecordVoiceCancel: icon {{ "send_control_record_active", attentionButtonFg }};
historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordVoiceRippleBgActive: lightButtonBgOver;
historyRecordVoiceRippleBgCancel: attentionButtonBgRipple; historyRecordVoiceRippleBgCancel: attentionButtonBgRipple;
historyRecordSignalColor: attentionButtonFg; historyRecordSignalColor: attentionButtonFg;
@ -345,6 +345,10 @@ historyRecordFont: font(13px);
historyRecordDurationSkip: 12px; historyRecordDurationSkip: 12px;
historyRecordDurationFg: historyComposeAreaFg; historyRecordDurationFg: historyComposeAreaFg;
historyRecordLevelMainRadius: 37px;
historyRecordLevelMinRadius: 38px;
historyRecordLevelMaxRadius: 60px;
historyRecordTextStyle: TextStyle(defaultTextStyle) { historyRecordTextStyle: TextStyle(defaultTextStyle) {
font: historyRecordFont; font: historyRecordFont;
} }