From f8039f9b9932f3734f00d330e2d4b1d14ce491c8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 06:00:07 +0300 Subject: [PATCH] Added cancel button to VoiceRecordBar. --- Telegram/Resources/langs/lang.strings | 1 - .../history_view_voice_record_bar.cpp | 184 +++++++++++++----- .../controls/history_view_voice_record_bar.h | 3 + Telegram/SourceFiles/ui/chat/chat.style | 3 + 4 files changed, 142 insertions(+), 49 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 667c07dac..4ced0747c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1343,7 +1343,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_record_cancel" = "Release outside this field to cancel"; -"lng_record_lock_cancel" = "Click outside of the circle to cancel"; "lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?"; "lng_record_lock_discard" = "Discard"; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 92f603fbf..3fff14c43 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -611,11 +611,14 @@ public: RecordLock(not_null parent); void requestPaintProgress(float64 progress); + void requestPaintLockToStopProgress(float64 progress); [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; [[nodiscard]] bool isStopState() const; + [[nodiscard]] float64 lockToStopProgress() const; + protected: QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; @@ -630,9 +633,9 @@ private: const QRect _rippleRect; const QPen _arcPen; - Ui::Animations::Simple _lockAnimation; Ui::Animations::Simple _lockEnderAnimation; + float64 _lockToStopProgress = 0.; rpl::variable _progress = 0.; }; @@ -662,8 +665,8 @@ void RecordLock::init() { if (!shown) { setCursor(style::cur_default); setAttribute(Qt::WA_TransparentForMouseEvents, true); - _lockAnimation.stop(); _lockEnderAnimation.stop(); + _lockToStopProgress = 0.; _progress = 0.; } }, lifetime()); @@ -675,32 +678,13 @@ void RecordLock::init() { const auto top = anim::interpolate( 0, height() - st::historyRecordLockTopShadow.height() * 2, - _lockAnimation.value(1.)); + _lockToStopProgress); p.translate(0, top); drawProgress(p); return; } drawProgress(p); }, lifetime()); - - locks( - ) | rpl::start_with_next([=] { - const auto &duration = st::historyRecordVoiceShowDuration; - const auto from = 0.; - const auto to = 1.; - auto callback = [=](float64 value) { - update(); - if (value == to) { - setCursor(style::cur_pointer); - setAttribute(Qt::WA_TransparentForMouseEvents, false); - - resize( - st::historyRecordLockTopShadow.width(), - st::historyRecordLockTopShadow.width()); - } - }; - _lockAnimation.start(std::move(callback), from, to, duration); - }, lifetime()); } void RecordLock::drawProgress(Painter &p) { @@ -773,8 +757,6 @@ void RecordLock::drawProgress(Painter &p) { } { PainterHighQualityEnabler hq(p); - const auto lockToStopProgress = - _lockAnimation.value(isLocked() ? 1. : 0); const auto &arcOffset = st::historyRecordLockIconLineSkip; const auto &size = st::historyRecordLockIconSize; @@ -786,15 +768,15 @@ void RecordLock::drawProgress(Painter &p) { const auto blockRectWidth = anim::interpolateF( size.width(), st::historyRecordStopIconWidth, - lockToStopProgress); + _lockToStopProgress); const auto blockRectHeight = anim::interpolateF( blockHeight, st::historyRecordStopIconWidth, - lockToStopProgress); + _lockToStopProgress); const auto blockRectTop = anim::interpolateF( size.height() - blockHeight, std::round((size.height() - blockRectHeight) / 2.), - lockToStopProgress); + _lockToStopProgress); const auto blockRect = QRectF( (size.width() - blockRectWidth) / 2, @@ -809,11 +791,11 @@ void RecordLock::drawProgress(Painter &p) { inner.x() + (inner.width() - size.width()) / 2, inner.y() + (originTop.height() * 2 - size.height()) / 2); { - const auto xRadius = anim::interpolate(2, 3, lockToStopProgress); + const auto xRadius = anim::interpolate(2, 3, _lockToStopProgress); p.drawRoundedRect(blockRect, xRadius, 3); } - const auto offsetTranslate = lockToStopProgress * + const auto offsetTranslate = _lockToStopProgress * (lineHeight + arcHeight + _arcPen.width() * 2); p.translate( size.width() - arcOffset, @@ -835,7 +817,7 @@ void RecordLock::drawProgress(Painter &p) { 0, 180 * 16); - const auto lockProgress = 1. - _lockAnimation.value(1.); + const auto lockProgress = 1. - _lockToStopProgress; if (progress == 1. && lockProgress < 1.) { p.drawLine( -arcWidth, @@ -866,6 +848,23 @@ void RecordLock::requestPaintProgress(float64 progress) { setProgress(progress); } +void RecordLock::requestPaintLockToStopProgress(float64 progress) { + _lockToStopProgress = progress; + if (isStopState()) { + setCursor(style::cur_pointer); + setAttribute(Qt::WA_TransparentForMouseEvents, false); + + resize( + st::historyRecordLockTopShadow.width(), + st::historyRecordLockTopShadow.width()); + } + update(); +} + +float64 RecordLock::lockToStopProgress() const { + return _lockToStopProgress; +} + void RecordLock::setProgress(float64 progress) { _progress = progress; update(); @@ -876,7 +875,7 @@ bool RecordLock::isLocked() const { } bool RecordLock::isStopState() const { - return isLocked() && (_lockAnimation.value(1.) == 1.); + return isLocked() && (_lockToStopProgress == 1.); } rpl::producer<> RecordLock::locks() const { @@ -892,6 +891,77 @@ QPoint RecordLock::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _rippleRect.topLeft(); } +class CancelButton final : public Ui::RippleButton { +public: + CancelButton(not_null parent, int height); + + void requestPaintProgress(float64 progress); + +protected: + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + void init(); + + const int _width; + const QRect _rippleRect; + + rpl::variable _showProgress = 0.; + + Ui::Text::String _text; + +}; + +CancelButton::CancelButton(not_null parent, int height) +: Ui::RippleButton(parent, st::defaultLightButton.ripple) +, _width(st::historyRecordCancelButtonWidth) +, _rippleRect(QRect(0, (height - _width) / 2, _width, _width)) +, _text(st::semiboldTextStyle, tr::lng_selected_clear(tr::now).toUpper()) { + resize(_width, height); + init(); +} + +void CancelButton::init() { + _showProgress.value( + ) | rpl::start_with_next([=](float64 progress) { + const auto hasProgress = (progress > 0.); + if (isHidden() == !hasProgress) { + setVisible(hasProgress); + } + update(); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + + p.setOpacity(_showProgress.current()); + + paintRipple(p, _rippleRect.x(), _rippleRect.y()); + + p.setPen(st::historyRecordCancelButtonFg); + _text.draw( + p, + 0, + (height() - _text.minHeight()) / 2, + width(), + style::al_center); + }, lifetime()); +} + +QImage CancelButton::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(_rippleRect.size()); +} + +QPoint CancelButton::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _rippleRect.topLeft(); +} + +void CancelButton::requestPaintProgress(float64 progress) { + _showProgress = progress; +} + VoiceRecordBar::VoiceRecordBar( not_null parent, not_null sectionWidget, @@ -906,7 +976,12 @@ VoiceRecordBar::VoiceRecordBar( , _level(std::make_unique( sectionWidget, _controller->widget()->leaveEvents())) +, _cancel(std::make_unique(this, recorderHeight)) , _startTimer([=] { startRecording(); }) +, _message( + st::historyRecordTextStyle, + tr::lng_record_cancel(tr::now), + TextParseOptions{ TextParseMultiline, 0, 0, Qt::LayoutDirectionAuto }) , _cancelFont(st::historyRecordFont) { resize(QSize(parent->width(), recorderHeight)); init(); @@ -993,6 +1068,7 @@ void VoiceRecordBar::init() { _cancelFont->width(FormatVoiceDuration(kMaxSamples)), ascent); } + _cancel->moveToLeft((size.width() - _cancel->width()) / 2, 0); updateMessageGeometry(); updateLockGeometry(); }, lifetime()); @@ -1006,6 +1082,14 @@ void VoiceRecordBar::init() { p.fillRect(clip, st::historyComposeAreaBg); p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio())); + const auto opacity = p.opacity(); + _cancel->requestPaintProgress(_lock->isStopState() + ? (opacity * _lock->lockToStopProgress()) + : 0.); + + if (!opacity) { + return; + } if (clip.intersects(_messageRect)) { // The message should be painted first to avoid flickering. drawMessage(p, activeAnimationRatio()); @@ -1056,7 +1140,8 @@ void VoiceRecordBar::init() { const auto &duration = st::historyRecordVoiceShowDuration; auto callback = [=](float64 value) { _listen->requestPaintProgress(value); - _level->requestPaintProgress(to - value); + const auto reverseValue = to - value; + _level->requestPaintProgress(reverseValue); update(); if (to == value) { _recordingLifetime.destroy(); @@ -1084,23 +1169,15 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=](bool enter) { _inField = enter; }, _recordingLifetime); - }, lifetime()); - rpl::merge( - _lock->locks(), - shownValue() | rpl::to_empty - ) | rpl::start_with_next([=] { - const auto direction = Qt::LayoutDirectionAuto; - _message.setText( - st::historyRecordTextStyle, - _lock->isLocked() - ? tr::lng_record_lock_cancel(tr::now) - : tr::lng_record_cancel(tr::now), - TextParseOptions{ TextParseMultiline, 0, 0, direction }); - - updateMessageGeometry(); - // Update a whole widget to clear a previous text. - update(); + const auto &duration = st::historyRecordVoiceShowDuration; + const auto from = 0.; + const auto to = 1.; + auto callback = [=](float64 value) { + _lock->requestPaintLockToStopProgress(value); + update(); + }; + _lockToStopAnimation.start(std::move(callback), from, to, duration); }, lifetime()); _send->events( @@ -1135,6 +1212,10 @@ void VoiceRecordBar::init() { installListenStateFilter(); }, lifetime()); + + _cancel->setClickedCallback([=] { + hideAnimated(); + }); } void VoiceRecordBar::activeAnimate(bool active) { @@ -1290,6 +1371,7 @@ void VoiceRecordBar::finish() { _recordingSamples = 0; _showAnimation.stop(); + _lockToStopAnimation.stop(); _listen = nullptr; @@ -1378,12 +1460,17 @@ void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { st::historyRecordCancelActive, 1. - recordActive)); + const auto opacity = p.opacity(); + p.setOpacity(opacity * (1. - _lock->lockToStopProgress())); + _message.draw( p, _messageRect.x(), _messageRect.y(), _messageRect.width(), style::al_center); + + p.setOpacity(opacity); } void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) { @@ -1421,6 +1508,7 @@ void VoiceRecordBar::hideAnimated() { if (isHidden()) { return; } + _lockShowing = false; visibilityAnimate(false, [=] { hideFast(); }); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 398bedb56..ff8e8cdbc 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -28,6 +28,7 @@ namespace HistoryView::Controls { class VoiceRecordButton; class ListenWrap; class RecordLock; +class CancelButton; class VoiceRecordBar final : public Ui::RpWidget { public: @@ -123,6 +124,7 @@ private: const std::shared_ptr _send; const std::unique_ptr _lock; const std::unique_ptr _level; + const std::unique_ptr _cancel; std::unique_ptr _listen; base::Timer _startTimer; @@ -152,6 +154,7 @@ private: rpl::lifetime _recordingLifetime; Ui::Animations::Simple _showLockAnimation; + Ui::Animations::Simple _lockToStopAnimation; Ui::Animations::Simple _showListenAnimation; Ui::Animations::Simple _activeAnimation; Ui::Animations::Simple _showAnimation; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a2b4a256d..320149008 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -395,6 +395,9 @@ historyRecordWaveformBar: 3px; historyRecordLockPosition: point(1px, 35px); +historyRecordCancelButtonWidth: 100px; +historyRecordCancelButtonFg: lightButtonFg; + historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }};