Added cancel button to VoiceRecordBar.

This commit is contained in:
23rd 2020-12-18 06:00:07 +03:00
parent 50ed60f443
commit f8039f9b99
4 changed files with 142 additions and 49 deletions

View file

@ -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";

View file

@ -611,11 +611,14 @@ public:
RecordLock(not_null<Ui::RpWidget*> 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<float64> _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<Ui::RpWidget*> 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<float64> _showProgress = 0.;
Ui::Text::String _text;
};
CancelButton::CancelButton(not_null<Ui::RpWidget*> 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<Ui::RpWidget*> parent,
not_null<Ui::RpWidget*> sectionWidget,
@ -906,7 +976,12 @@ VoiceRecordBar::VoiceRecordBar(
, _level(std::make_unique<VoiceRecordButton>(
sectionWidget,
_controller->widget()->leaveEvents()))
, _cancel(std::make_unique<CancelButton>(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(); });
}

View file

@ -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<Ui::SendButton> _send;
const std::unique_ptr<RecordLock> _lock;
const std::unique_ptr<VoiceRecordButton> _level;
const std::unique_ptr<CancelButton> _cancel;
std::unique_ptr<ListenWrap> _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;

View file

@ -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 }};