Finish theming for voice recording in stories.

This commit is contained in:
John Preston 2023-05-18 12:56:15 +04:00
parent a02876562a
commit 00b4f77384
10 changed files with 244 additions and 123 deletions

View File

@ -139,6 +139,29 @@ SendButton {
sendDisabledFg: color; sendDisabledFg: color;
} }
RecordBarLock {
ripple: RippleAnimation;
originTop: icon;
originBottom: icon;
originBody: icon;
shadowTop: icon;
shadowBottom: icon;
shadowBody: icon;
arrow: icon;
fg: color;
}
RecordBar {
radius: pixels;
bg: color;
durationFg: color;
cancel: color;
cancelActive: color;
cancelRipple: RippleAnimation;
lock: RecordBarLock;
remove: IconButton;
}
ComposeControls { ComposeControls {
bg: color; bg: color;
radius: pixels; radius: pixels;
@ -149,6 +172,7 @@ ComposeControls {
emoji: EmojiButton; emoji: EmojiButton;
suggestions: EmojiSuggestions; suggestions: EmojiSuggestions;
tabbed: EmojiPan; tabbed: EmojiPan;
record: RecordBar;
} }
switchPmButton: RoundButton(defaultBoxButton) { switchPmButton: RoundButton(defaultBoxButton) {
@ -875,7 +899,6 @@ historyRecordTextRight: 25px;
historyRecordLockShowDuration: historyToDownDuration; historyRecordLockShowDuration: historyToDownDuration;
historyRecordLockSize: size(75px, 133px); historyRecordLockSize: size(75px, 133px);
historyRecordLockIconFg: historyToDownFg;
historyRecordLockIconSize: size(14px, 17px); historyRecordLockIconSize: size(14px, 17px);
historyRecordLockIconBottomHeight: 9px; historyRecordLockIconBottomHeight: 9px;
historyRecordLockIconLineHeight: 2px; historyRecordLockIconLineHeight: 2px;
@ -916,6 +939,29 @@ historySilentToggle: IconButton(historyBotKeyboardShow) {
historySilentToggleOn: icon {{ "chat/input_silent_on", historyComposeIconFg }}; historySilentToggleOn: icon {{ "chat/input_silent_on", historyComposeIconFg }};
historySilentToggleOnOver: icon {{ "chat/input_silent_on", historyComposeIconFgOver }}; historySilentToggleOnOver: icon {{ "chat/input_silent_on", historyComposeIconFgOver }};
defaultRecordBarLock: RecordBarLock {
ripple: defaultRippleAnimation;
originTop: historyRecordLockTop;
originBottom: historyRecordLockBottom;
originBody: historyRecordLockBody;
shadowTop: historyRecordLockTopShadow;
shadowBottom: historyRecordLockBottomShadow;
shadowBody: historyRecordLockBodyShadow;
arrow: historyRecordLockArrow;
fg: historyToDownFg;
}
defaultRecordBar: RecordBar {
bg: historyComposeAreaBg;
durationFg: historyRecordDurationFg;
cancel: historyRecordCancel;
cancelActive: historyRecordCancelActive;
cancelRipple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgRipple;
}
lock: defaultRecordBarLock;
remove: historyRecordDelete;
}
historySend: SendButton { historySend: SendButton {
inner: IconButton(historyAttach) { inner: IconButton(historyAttach) {
icon: historySendIcon; icon: historySendIcon;
@ -936,4 +982,5 @@ defaultComposeControls: ComposeControls {
emoji: historyAttachEmoji; emoji: historyAttachEmoji;
suggestions: defaultEmojiSuggestions; suggestions: defaultEmojiSuggestions;
tabbed: defaultEmojiPan; tabbed: defaultEmojiPan;
record: defaultRecordBar;
} }

View File

@ -943,18 +943,6 @@ void HistoryWidget::refreshTabbedPanel() {
} }
void HistoryWidget::initVoiceRecordBar() { void HistoryWidget::initVoiceRecordBar() {
{
auto scrollHeight = rpl::combine(
_scroll->topValue(),
_scroll->heightValue()
) | rpl::map([](int top, int height) {
return top + height - st::historyRecordLockPosition.y();
});
_voiceRecordBar->setLockBottom(std::move(scrollHeight));
}
_voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue());
_voiceRecordBar->setStartRecordingFilter([=] { _voiceRecordBar->setStartRecordingFilter([=] {
const auto error = [&]() -> std::optional<QString> { const auto error = [&]() -> std::optional<QString> {
if (_peer) { if (_peer) {

View File

@ -989,10 +989,14 @@ ComposeControls::ComposeControls(
, _header(std::make_unique<FieldHeader>(_wrap.get(), _show)) , _header(std::make_unique<FieldHeader>(_wrap.get(), _show))
, _voiceRecordBar(std::make_unique<VoiceRecordBar>( , _voiceRecordBar(std::make_unique<VoiceRecordBar>(
_wrap.get(), _wrap.get(),
parent, Controls::VoiceRecordBarDescriptor{
_show, .outerContainer = parent,
_send, .show = _show,
st::historySendSize.height())) .send = _send,
.stOverride = &_st.record,
.recorderHeight = st::historySendSize.height(),
.lockFromBottom = descriptor.voiceLockFromBottom,
}))
, _sendMenuType(descriptor.sendMenuType) , _sendMenuType(descriptor.sendMenuType)
, _unavailableEmojiPasted(std::move(descriptor.unavailableEmojiPasted)) , _unavailableEmojiPasted(std::move(descriptor.unavailableEmojiPasted))
, _saveDraftTimer([=] { saveDraft(); }) , _saveDraftTimer([=] { saveDraft(); })
@ -2278,26 +2282,6 @@ void ComposeControls::initVoiceRecordBar() {
return false; return false;
}); });
{
auto geometry = rpl::merge(
_wrap->geometryValue(),
_send->geometryValue()
) | rpl::map([=](QRect geometry) {
auto r = _send->geometry();
r.setY(r.y() + _wrap->y());
return r;
});
_voiceRecordBar->setSendButtonGeometryValue(std::move(geometry));
}
{
auto bottom = _wrap->geometryValue(
) | rpl::map([=](QRect geometry) {
return geometry.y() - st::historyRecordLockPosition.y();
});
_voiceRecordBar->setLockBottom(std::move(bottom));
}
_voiceRecordBar->updateSendButtonTypeRequests( _voiceRecordBar->updateSendButtonTypeRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updateSendButtonType(); updateSendButtonType();

View File

@ -102,6 +102,7 @@ struct ComposeControlsDescriptor {
SendMenu::Type sendMenuType = {}; SendMenu::Type sendMenuType = {};
Window::SessionController *regularWindow = nullptr; Window::SessionController *regularWindow = nullptr;
rpl::producer<ChatHelpers::FileChosen> stickerOrEmojiChosen; rpl::producer<ChatHelpers::FileChosen> stickerOrEmojiChosen;
bool voiceLockFromBottom = false;
ChatHelpers::ComposeFeatures features; ChatHelpers::ComposeFeatures features;
}; };

View File

@ -27,17 +27,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio_capture.h" #include "media/audio/media_audio_capture.h"
#include "media/player/media_player_button.h" #include "media/player/media_player_button.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_media_player.h"
#include "ui/controls/send_button.h" #include "ui/controls/send_button.h"
#include "ui/effects/animation_value.h" #include "ui/effects/animation_value.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_media_player.h"
namespace HistoryView::Controls { namespace HistoryView::Controls {
namespace { namespace {
using SendActionUpdate = VoiceRecordBar::SendActionUpdate; using SendActionUpdate = VoiceRecordBar::SendActionUpdate;
@ -206,6 +206,7 @@ class ListenWrap final {
public: public:
ListenWrap( ListenWrap(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
const style::RecordBar &st,
not_null<Main::Session*> session, not_null<Main::Session*> session,
::Media::Capture::Result &&data, ::Media::Capture::Result &&data,
const style::font &font); const style::font &font);
@ -231,12 +232,12 @@ private:
not_null<Ui::RpWidget*> _parent; not_null<Ui::RpWidget*> _parent;
const style::RecordBar &_st;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
const not_null<DocumentData*> _document; const not_null<DocumentData*> _document;
const std::unique_ptr<VoiceData> _voiceData; const std::unique_ptr<VoiceData> _voiceData;
const std::shared_ptr<Data::DocumentMedia> _mediaView; const std::shared_ptr<Data::DocumentMedia> _mediaView;
const std::unique_ptr<::Media::Capture::Result> _data; const std::unique_ptr<::Media::Capture::Result> _data;
const style::IconButton &_stDelete;
const base::unique_qptr<Ui::IconButton> _delete; const base::unique_qptr<Ui::IconButton> _delete;
const style::font &_durationFont; const style::font &_durationFont;
const QString _duration; const QString _duration;
@ -264,17 +265,18 @@ private:
ListenWrap::ListenWrap( ListenWrap::ListenWrap(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
const style::RecordBar &st,
not_null<Main::Session*> session, not_null<Main::Session*> session,
::Media::Capture::Result &&data, ::Media::Capture::Result &&data,
const style::font &font) const style::font &font)
: _parent(parent) : _parent(parent)
, _st(st)
, _session(session) , _session(session)
, _document(DummyDocument(&session->data())) , _document(DummyDocument(&session->data()))
, _voiceData(ProcessCaptureResult(data)) , _voiceData(ProcessCaptureResult(data))
, _mediaView(_document->createMediaView()) , _mediaView(_document->createMediaView())
, _data(std::make_unique<::Media::Capture::Result>(std::move(data))) , _data(std::make_unique<::Media::Capture::Result>(std::move(data)))
, _stDelete(st::historyRecordDelete) , _delete(base::make_unique_q<Ui::IconButton>(parent, _st.remove))
, _delete(base::make_unique_q<Ui::IconButton>(parent, _stDelete))
, _durationFont(font) , _durationFont(font)
, _duration(Ui::FormatDurationText( , _duration(Ui::FormatDurationText(
float64(_data->samples) / ::Media::Player::kDefaultFrequency)) float64(_data->samples) / ::Media::Player::kDefaultFrequency))
@ -299,7 +301,7 @@ void ListenWrap::init() {
_waveformBgRect = QRect({ 0, 0 }, size) _waveformBgRect = QRect({ 0, 0 }, size)
.marginsRemoved(st::historyRecordWaveformBgMargins); .marginsRemoved(st::historyRecordWaveformBgMargins);
{ {
const auto m = _stDelete.width + _waveformBgRect.height() / 2; const auto m = _st.remove.width + _waveformBgRect.height() / 2;
_waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved( _waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved(
style::margins(m, 0, m, 0)); style::margins(m, 0, m, 0));
} }
@ -319,22 +321,23 @@ void ListenWrap::init() {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
const auto progress = _showProgress.current(); const auto progress = _showProgress.current();
p.setOpacity(progress); p.setOpacity(progress);
const auto &remove = _st.remove;
if (progress > 0. && progress < 1.) { if (progress > 0. && progress < 1.) {
_stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width()); remove.icon.paint(p, remove.iconPosition, _parent->width());
} }
{ {
const auto hideOffset = _isShowAnimation const auto hideOffset = _isShowAnimation
? 0 ? 0
: anim::interpolate(kHideWaveformBgOffset, 0, progress); : anim::interpolate(kHideWaveformBgOffset, 0, progress);
const auto deleteIconLeft = _stDelete.iconPosition.x(); const auto deleteIconLeft = remove.iconPosition.x();
const auto bgRectRight = anim::interpolate( const auto bgRectRight = anim::interpolate(
deleteIconLeft, deleteIconLeft,
_stDelete.width, remove.width,
_isShowAnimation ? progress : 1.); _isShowAnimation ? progress : 1.);
const auto bgRectLeft = anim::interpolate( const auto bgRectLeft = anim::interpolate(
_parent->width() - deleteIconLeft - _waveformBgRect.height(), _parent->width() - deleteIconLeft - _waveformBgRect.height(),
_stDelete.width, remove.width,
_isShowAnimation ? progress : 1.); _isShowAnimation ? progress : 1.);
const auto bgRectMargins = style::margins( const auto bgRectMargins = style::margins(
bgRectLeft - hideOffset, bgRectLeft - hideOffset,
@ -357,7 +360,7 @@ void ListenWrap::init() {
p.setOpacity(progress); p.setOpacity(progress);
} }
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::historyRecordCancelActive); p.setBrush(_st.cancelActive);
QPainterPath path; QPainterPath path;
path.setFillRule(Qt::WindingFill); path.setFillRule(Qt::WindingFill);
path.addEllipse(bgLeftCircleRect); path.addEllipse(bgLeftCircleRect);
@ -605,10 +608,13 @@ rpl::lifetime &ListenWrap::lifetime() {
class RecordLock final : public Ui::RippleButton { class RecordLock final : public Ui::RippleButton {
public: public:
RecordLock(not_null<Ui::RpWidget*> parent); RecordLock(
not_null<Ui::RpWidget*> parent,
const style::RecordBarLock &st);
void requestPaintProgress(float64 progress); void requestPaintProgress(float64 progress);
void requestPaintLockToStopProgress(float64 progress); void requestPaintLockToStopProgress(float64 progress);
void setVisibleTopPart(int part);
[[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] rpl::producer<> locks() const;
[[nodiscard]] bool isLocked() const; [[nodiscard]] bool isLocked() const;
@ -627,6 +633,7 @@ private:
void setProgress(float64 progress); void setProgress(float64 progress);
void startLockingAnimation(float64 to); void startLockingAnimation(float64 to);
const style::RecordBarLock &_st;
const QRect _rippleRect; const QRect _rippleRect;
const QPen _arcPen; const QPen _arcPen;
@ -634,10 +641,15 @@ private:
float64 _lockToStopProgress = 0.; float64 _lockToStopProgress = 0.;
rpl::variable<float64> _progress = 0.; rpl::variable<float64> _progress = 0.;
int _visibleTopPart = -1;
}; };
RecordLock::RecordLock(not_null<Ui::RpWidget*> parent) RecordLock::RecordLock(
: RippleButton(parent, st::defaultRippleAnimation) not_null<Ui::RpWidget*> parent,
const style::RecordBarLock &st)
: RippleButton(parent, st.ripple)
, _st(st)
, _rippleRect(QRect( , _rippleRect(QRect(
0, 0,
0, 0,
@ -653,6 +665,10 @@ RecordLock::RecordLock(not_null<Ui::RpWidget*> parent)
init(); init();
} }
void RecordLock::setVisibleTopPart(int part) {
_visibleTopPart = part;
}
void RecordLock::init() { void RecordLock::init() {
shownValue( shownValue(
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
@ -670,7 +686,13 @@ void RecordLock::init() {
paintRequest( paintRequest(
) | rpl::start_with_next([=](const QRect &clip) { ) | rpl::start_with_next([=](const QRect &clip) {
if (!_visibleTopPart) {
return;
}
Painter p(this); Painter p(this);
if (_visibleTopPart > 0 && _visibleTopPart < height()) {
p.setClipRect(0, 0, width(), _visibleTopPart);
}
if (isLocked()) { if (isLocked()) {
const auto top = anim::interpolate( const auto top = anim::interpolate(
0, 0,
@ -687,12 +709,12 @@ void RecordLock::init() {
void RecordLock::drawProgress(Painter &p) { void RecordLock::drawProgress(Painter &p) {
const auto progress = _progress.current(); const auto progress = _progress.current();
const auto &originTop = st::historyRecordLockTop; const auto &originTop = _st.originTop;
const auto &originBottom = st::historyRecordLockBottom; const auto &originBottom = _st.originBottom;
const auto &originBody = st::historyRecordLockBody; const auto &originBody = _st.originBody;
const auto &shadowTop = st::historyRecordLockTopShadow; const auto &shadowTop = _st.shadowTop;
const auto &shadowBottom = st::historyRecordLockBottomShadow; const auto &shadowBottom = _st.shadowBottom;
const auto &shadowBody = st::historyRecordLockBodyShadow; const auto &shadowBody = _st.shadowBody;
const auto &shadowMargins = st::historyRecordLockMargin; const auto &shadowMargins = st::historyRecordLockMargin;
const auto bottomMargin = anim::interpolate( const auto bottomMargin = anim::interpolate(
@ -739,7 +761,7 @@ void RecordLock::drawProgress(Painter &p) {
originBody.fill(p, content); originBody.fill(p, content);
} }
{ {
const auto &arrow = st::historyRecordLockArrow; const auto &arrow = _st.arrow;
const auto arrowRect = QRect( const auto arrowRect = QRect(
inner.x(), inner.x(),
content.y() + content.height() - arrow.height() / 2, content.y() + content.height() - arrow.height() / 2,
@ -791,7 +813,7 @@ void RecordLock::drawProgress(Painter &p) {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
p.translate(inner.topLeft() + lockTranslation); p.translate(inner.topLeft() + lockTranslation);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::historyRecordLockIconFg); p.setBrush(_st.fg);
p.drawRoundedRect(blockRect, xRadius, 3); p.drawRoundedRect(blockRect, xRadius, 3);
} else { } else {
// Paint an animation frame. // Paint an animation frame.
@ -844,7 +866,7 @@ void RecordLock::drawProgress(Painter &p) {
p.drawImage( p.drawImage(
inner.topLeft(), inner.topLeft(),
style::colorizeImage(frame, st::historyRecordLockIconFg)); style::colorizeImage(frame, _st.fg));
} }
} }
} }
@ -914,7 +936,10 @@ QPoint RecordLock::prepareRippleStartPosition() const {
class CancelButton final : public Ui::RippleButton { class CancelButton final : public Ui::RippleButton {
public: public:
CancelButton(not_null<Ui::RpWidget*> parent, int height); CancelButton(
not_null<Ui::RpWidget*> parent,
const style::RecordBar &st,
int height);
void requestPaintProgress(float64 progress); void requestPaintProgress(float64 progress);
@ -925,6 +950,7 @@ protected:
private: private:
void init(); void init();
const style::RecordBar &_st;
const int _width; const int _width;
const QRect _rippleRect; const QRect _rippleRect;
@ -934,8 +960,12 @@ private:
}; };
CancelButton::CancelButton(not_null<Ui::RpWidget*> parent, int height) CancelButton::CancelButton(
: Ui::RippleButton(parent, st::defaultLightButton.ripple) not_null<Ui::RpWidget*> parent,
const style::RecordBar &st,
int height)
: Ui::RippleButton(parent, st.cancelRipple)
, _st(st)
, _width(st::historyRecordCancelButtonWidth) , _width(st::historyRecordCancelButtonWidth)
, _rippleRect(QRect(0, (height - _width) / 2, _width, _width)) , _rippleRect(QRect(0, (height - _width) / 2, _width, _width))
, _text(st::semiboldTextStyle, tr::lng_selected_clear(tr::now)) { , _text(st::semiboldTextStyle, tr::lng_selected_clear(tr::now)) {
@ -958,7 +988,7 @@ void CancelButton::init() {
paintRipple(p, _rippleRect.x(), _rippleRect.y()); paintRipple(p, _rippleRect.x(), _rippleRect.y());
p.setPen(st::historyRecordCancelButtonFg); p.setPen(_st.cancelActive);
_text.draw( _text.draw(
p, p,
0, 0,
@ -983,24 +1013,23 @@ void CancelButton::requestPaintProgress(float64 progress) {
VoiceRecordBar::VoiceRecordBar( VoiceRecordBar::VoiceRecordBar(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<Ui::RpWidget*> sectionWidget, VoiceRecordBarDescriptor &&descriptor)
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Ui::SendButton> send,
int recorderHeight)
: RpWidget(parent) : RpWidget(parent)
, _sectionWidget(sectionWidget) , _st(descriptor.stOverride ? *descriptor.stOverride : st::defaultRecordBar)
, _show(std::move(show)) , _outerContainer(descriptor.outerContainer)
, _send(send) , _show(std::move(descriptor.show))
, _lock(std::make_unique<RecordLock>(sectionWidget)) , _send(std::move(descriptor.send))
, _level(std::make_unique<VoiceRecordButton>(sectionWidget)) , _lock(std::make_unique<RecordLock>(_outerContainer, _st.lock))
, _cancel(std::make_unique<CancelButton>(this, recorderHeight)) , _level(std::make_unique<VoiceRecordButton>(_outerContainer, _st))
, _cancel(std::make_unique<CancelButton>(this, _st, descriptor.recorderHeight))
, _startTimer([=] { startRecording(); }) , _startTimer([=] { startRecording(); })
, _message( , _message(
st::historyRecordTextStyle, st::historyRecordTextStyle,
tr::lng_record_cancel(tr::now), tr::lng_record_cancel(tr::now),
TextParseOptions{ TextParseMultiline, 0, 0, Qt::LayoutDirectionAuto }) TextParseOptions{ TextParseMultiline, 0, 0, Qt::LayoutDirectionAuto })
, _lockFromBottom(descriptor.lockFromBottom)
, _cancelFont(st::historyRecordFont) { , _cancelFont(st::historyRecordFont) {
resize(QSize(parent->width(), recorderHeight)); resize(QSize(parent->width(), descriptor.recorderHeight));
init(); init();
hideFast(); hideFast();
} }
@ -1010,7 +1039,12 @@ VoiceRecordBar::VoiceRecordBar(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Ui::SendButton> send, std::shared_ptr<Ui::SendButton> send,
int recorderHeight) int recorderHeight)
: VoiceRecordBar(parent, parent, std::move(show), send, recorderHeight) { : VoiceRecordBar(parent, {
.outerContainer = parent,
.show = std::move(show),
.send = std::move(send),
.recorderHeight = recorderHeight,
}) {
} }
VoiceRecordBar::~VoiceRecordBar() { VoiceRecordBar::~VoiceRecordBar() {
@ -1040,14 +1074,32 @@ void VoiceRecordBar::updateMessageGeometry() {
} }
void VoiceRecordBar::updateLockGeometry() { void VoiceRecordBar::updateLockGeometry() {
const auto right = anim::interpolate( const auto parent = parentWidget();
-_lock->width(), const auto me = Ui::MapFrom(_outerContainer, parent, geometry());
st::historyRecordLockPosition.x(), const auto finalTop = me.y()
_showLockAnimation.value(_lockShowing.current() ? 1. : 0.)); - st::historyRecordLockPosition.y()
_lock->moveToRight(right, _lock->y()); - _lock->height();
const auto finalRight = _outerContainer->width()
- (me.x() + me.width())
+ st::historyRecordLockPosition.x();
const auto progress = _showLockAnimation.value(
_lockShowing.current() ? 1. : 0.);
if (_lockFromBottom) {
const auto top = anim::interpolate(me.y(), finalTop, progress);
_lock->moveToRight(finalRight, top);
_lock->setVisibleTopPart(me.y() - top);
} else {
const auto from = -_lock->width();
const auto right = anim::interpolate(from, finalRight, progress);
_lock->moveToRight(right, finalTop);
}
} }
void VoiceRecordBar::init() { void VoiceRecordBar::init() {
if (_st.radius > 0) {
_backgroundRect.emplace(_st.radius, _st.bg);
}
// Keep VoiceRecordBar behind SendButton. // Keep VoiceRecordBar behind SendButton.
rpl::single( rpl::single(
) | rpl::then( ) | rpl::then(
@ -1087,7 +1139,6 @@ void VoiceRecordBar::init() {
} }
_cancel->moveToLeft((size.width() - _cancel->width()) / 2, 0); _cancel->moveToLeft((size.width() - _cancel->width()) / 2, 0);
updateMessageGeometry(); updateMessageGeometry();
updateLockGeometry();
}, lifetime()); }, lifetime());
paintRequest( paintRequest(
@ -1096,7 +1147,11 @@ void VoiceRecordBar::init() {
if (_showAnimation.animating()) { if (_showAnimation.animating()) {
p.setOpacity(showAnimationRatio()); p.setOpacity(showAnimationRatio());
} }
p.fillRect(clip, st::historyComposeAreaBg); if (_backgroundRect) {
_backgroundRect->paint(p, rect());
} else {
p.fillRect(clip, _st.bg);
}
p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio())); p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio()));
const auto opacity = p.opacity(); const auto opacity = p.opacity();
@ -1237,6 +1292,9 @@ void VoiceRecordBar::init() {
_cancel->setClickedCallback([=] { _cancel->setClickedCallback([=] {
hideAnimated(); hideAnimated();
}); });
initLockGeometry();
initLevelGeometry();
} }
void VoiceRecordBar::activeAnimate(bool active) { void VoiceRecordBar::activeAnimate(bool active) {
@ -1278,22 +1336,25 @@ void VoiceRecordBar::setStartRecordingFilter(Fn<bool()> &&callback) {
_startRecordingFilter = std::move(callback); _startRecordingFilter = std::move(callback);
} }
void VoiceRecordBar::setLockBottom(rpl::producer<int> &&bottom) { void VoiceRecordBar::initLockGeometry() {
rpl::combine( rpl::combine(
std::move(bottom), _lock->heightValue(),
_lock->sizeValue() | rpl::map_to(true) // Dummy value. geometryValue(),
) | rpl::start_with_next([=](int value, bool dummy) { static_cast<Ui::RpWidget*>(parentWidget())->geometryValue()
_lock->moveToLeft(_lock->x(), value - _lock->height()); ) | rpl::start_with_next([=] {
updateLockGeometry();
}, lifetime()); }, lifetime());
} }
void VoiceRecordBar::setSendButtonGeometryValue( void VoiceRecordBar::initLevelGeometry() {
rpl::producer<QRect> &&geometry) { rpl::combine(
std::move( _send->geometryValue(),
geometry geometryValue(),
) | rpl::start_with_next([=](QRect r) { static_cast<Ui::RpWidget*>(parentWidget())->geometryValue()
const auto center = (r.width() - _level->width()) / 2; ) | rpl::start_with_next([=](QRect send, QRect me, QRect parent) {
_level->moveToLeft(r.x() + center, r.y() + center); const auto mapped = Ui::MapFrom(_outerContainer, this, send);
const auto center = (send.width() - _level->width()) / 2;
_level->moveToLeft(mapped.x() + center, mapped.y() + center);
}, lifetime()); }, lifetime());
} }
@ -1431,6 +1492,7 @@ void VoiceRecordBar::stopRecording(StopType type) {
} else if (type == StopType::Listen) { } else if (type == StopType::Listen) {
_listen = std::make_unique<ListenWrap>( _listen = std::make_unique<ListenWrap>(
this, this,
_st,
&_show->session(), &_show->session(),
std::move(data), std::move(data),
_cancelFont); _cancelFont);
@ -1444,7 +1506,7 @@ void VoiceRecordBar::stopRecording(StopType type) {
void VoiceRecordBar::drawDuration(Painter &p) { void VoiceRecordBar::drawDuration(Painter &p) {
const auto duration = FormatVoiceDuration(_recordingSamples); const auto duration = FormatVoiceDuration(_recordingSamples);
p.setFont(_cancelFont); p.setFont(_cancelFont);
p.setPen(st::historyRecordDurationFg); p.setPen(_st.durationFg);
p.drawText(_durationRect, style::al_left, duration); p.drawText(_durationRect, style::al_left, duration);
} }
@ -1478,11 +1540,7 @@ void VoiceRecordBar::drawRedCircle(Painter &p) {
} }
void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) {
p.setPen( p.setPen(anim::pen(_st.cancel, _st.cancelActive, 1. - recordActive));
anim::pen(
st::historyRecordCancel,
st::historyRecordCancelActive,
1. - recordActive));
const auto opacity = p.opacity(); const auto opacity = p.opacity();
p.setOpacity(opacity * (1. - _lock->lockToStopProgress())); p.setOpacity(opacity * (1. - _lock->lockToStopProgress()));
@ -1621,8 +1679,8 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
void VoiceRecordBar::orderControls() { void VoiceRecordBar::orderControls() {
stackUnder(_send.get()); stackUnder(_send.get());
_level->raise();
_lock->raise(); _lock->raise();
_level->raise();
} }
void VoiceRecordBar::installListenStateFilter() { void VoiceRecordBar::installListenStateFilter() {

View File

@ -11,10 +11,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "history/view/controls/compose_controls_common.h" #include "history/view/controls/compose_controls_common.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/round_rect.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
struct VoiceData; struct VoiceData;
namespace style {
struct RecordBar;
} // namespace style
namespace Ui { namespace Ui {
class SendButton; class SendButton;
} // namespace Ui } // namespace Ui
@ -34,6 +39,15 @@ class ListenWrap;
class RecordLock; class RecordLock;
class CancelButton; class CancelButton;
struct VoiceRecordBarDescriptor {
not_null<Ui::RpWidget*> outerContainer;
std::shared_ptr<ChatHelpers::Show> show;
std::shared_ptr<Ui::SendButton> send;
const style::RecordBar *stOverride = nullptr;
int recorderHeight = 0;
bool lockFromBottom = false;
};
class VoiceRecordBar final : public Ui::RpWidget { class VoiceRecordBar final : public Ui::RpWidget {
public: public:
using SendActionUpdate = Controls::SendActionUpdate; using SendActionUpdate = Controls::SendActionUpdate;
@ -41,10 +55,7 @@ public:
VoiceRecordBar( VoiceRecordBar(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<Ui::RpWidget*> sectionWidget, VoiceRecordBarDescriptor &&descriptor);
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Ui::SendButton> send,
int recorderHeight);
VoiceRecordBar( VoiceRecordBar(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
@ -75,8 +86,6 @@ public:
void requestToSendWithOptions(Api::SendOptions options); void requestToSendWithOptions(Api::SendOptions options);
void setLockBottom(rpl::producer<int> &&bottom);
void setSendButtonGeometryValue(rpl::producer<QRect> &&geometry);
void setStartRecordingFilter(Fn<bool()> &&callback); void setStartRecordingFilter(Fn<bool()> &&callback);
[[nodiscard]] bool isRecording() const; [[nodiscard]] bool isRecording() const;
@ -93,6 +102,8 @@ private:
}; };
void init(); void init();
void initLockGeometry();
void initLevelGeometry();
void updateMessageGeometry(); void updateMessageGeometry();
void updateLockGeometry(); void updateLockGeometry();
@ -125,7 +136,8 @@ private:
void computeAndSetLockProgress(QPoint globalPos); void computeAndSetLockProgress(QPoint globalPos);
const not_null<Ui::RpWidget*> _sectionWidget; const style::RecordBar &_st;
const not_null<Ui::RpWidget*> _outerContainer;
const std::shared_ptr<ChatHelpers::Show> _show; const std::shared_ptr<ChatHelpers::Show> _show;
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;
@ -159,11 +171,13 @@ private:
rpl::event_stream<> _recordingTipRequests; rpl::event_stream<> _recordingTipRequests;
bool _recordingTipRequired = false; bool _recordingTipRequired = false;
bool _lockFromBottom = false;
const style::font &_cancelFont; const style::font &_cancelFont;
rpl::lifetime _recordingLifetime; rpl::lifetime _recordingLifetime;
std::optional<Ui::RoundRect> _backgroundRect;
Ui::Animations::Simple _showLockAnimation; Ui::Animations::Simple _showLockAnimation;
Ui::Animations::Simple _lockToStopAnimation; Ui::Animations::Simple _lockToStopAnimation;
Ui::Animations::Simple _showListenAnimation; Ui::Animations::Simple _showListenAnimation;

View File

@ -47,8 +47,11 @@ auto Blobs() {
} // namespace } // namespace
VoiceRecordButton::VoiceRecordButton(not_null<Ui::RpWidget*> parent) VoiceRecordButton::VoiceRecordButton(
not_null<Ui::RpWidget*> parent,
const style::RecordBar &st)
: AbstractButton(parent) : AbstractButton(parent)
, _st(st)
, _blobs(std::make_unique<Ui::Paint::Blobs>( , _blobs(std::make_unique<Ui::Paint::Blobs>(
Blobs(), Blobs(),
kLevelDuration, kLevelDuration,

View File

@ -11,17 +11,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
namespace Ui { namespace style {
namespace Paint { struct RecordBar;
} // namespace style
namespace Ui::Paint {
class Blobs; class Blobs;
} // namespace Paint } // namespace Ui::Paint
} // namespace Ui
namespace HistoryView::Controls { namespace HistoryView::Controls {
class VoiceRecordButton final : public Ui::AbstractButton { class VoiceRecordButton final : public Ui::AbstractButton {
public: public:
explicit VoiceRecordButton(not_null<Ui::RpWidget*> parent); VoiceRecordButton(
not_null<Ui::RpWidget*> parent,
const style::RecordBar &st);
~VoiceRecordButton(); ~VoiceRecordButton();
enum class Type { enum class Type {
@ -43,6 +47,7 @@ public:
private: private:
void init(); void init();
const style::RecordBar &_st;
std::unique_ptr<Ui::Paint::Blobs> _blobs; std::unique_ptr<Ui::Paint::Blobs> _blobs;
crl::time _lastUpdateTime = 0; crl::time _lastUpdateTime = 0;

View File

@ -35,6 +35,7 @@ ReplyArea::ReplyArea(not_null<Controller*> controller)
.mode = HistoryView::ComposeControlsMode::Normal, .mode = HistoryView::ComposeControlsMode::Normal,
.sendMenuType = SendMenu::Type::SilentOnly, .sendMenuType = SendMenu::Type::SilentOnly,
.stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(),
.voiceLockFromBottom = true,
.features = { .features = {
.sendAs = false, .sendAs = false,
.ttlInfo = false, .ttlInfo = false,

View File

@ -448,24 +448,23 @@ storiesComposeWhiteText: groupCallMembersFg;
storiesComposeGrayText: groupCallMemberNotJoinedStatus; storiesComposeGrayText: groupCallMemberNotJoinedStatus;
storiesComposeGrayIcon: groupCallMemberInactiveIcon; storiesComposeGrayIcon: groupCallMemberInactiveIcon;
storiesComposeBlue: groupCallActiveFg; storiesComposeBlue: groupCallActiveFg;
storiesComposeRippleLight: RippleAnimation(defaultRippleAnimation) {
color: storiesComposeBgOver;
}
storiesComposeRipple: RippleAnimation(defaultRippleAnimation) { storiesComposeRipple: RippleAnimation(defaultRippleAnimation) {
color: groupCallMembersBgRipple; color: groupCallMembersBgRipple;
} }
storiesAttach: IconButton(historyAttach) { storiesAttach: IconButton(historyAttach) {
icon: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; icon: icon {{ "chat/input_attach", storiesComposeGrayIcon }};
iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }};
ripple: RippleAnimation(defaultRippleAnimation) { ripple: storiesComposeRippleLight;
color: storiesComposeBgOver;
}
} }
storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }};
storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }};
storiesRemoveSet: IconButton(stickerPanRemoveSet) { storiesRemoveSet: IconButton(stickerPanRemoveSet) {
icon: icon {{ "simple_close", storiesComposeGrayIcon }}; icon: icon {{ "simple_close", storiesComposeGrayIcon }};
iconOver: icon {{ "simple_close", storiesComposeGrayIcon }}; iconOver: icon {{ "simple_close", storiesComposeGrayIcon }};
ripple: RippleAnimation(defaultRippleAnimation) { ripple: storiesComposeRippleLight;
color: storiesComposeBgOver;
}
} }
storiesMenu: Menu(defaultMenu) { storiesMenu: Menu(defaultMenu) {
itemBg: groupCallMenuBg; itemBg: groupCallMenuBg;
@ -641,4 +640,25 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
} }
autocompleteBottomSkip: 10px; autocompleteBottomSkip: 10px;
} }
record: RecordBar(defaultRecordBar) {
bg: storiesComposeBg;
durationFg: storiesComposeWhiteText;
radius: storiesRadius;
cancel: storiesComposeGrayText;
cancelActive: storiesComposeBlue;
cancelRipple: storiesComposeRippleLight;
lock: RecordBarLock(defaultRecordBarLock) {
ripple: storiesComposeRipple;
originTop: icon {{ "voice_lock/record_lock_top", storiesComposeBg }};
originBottom: icon {{ "voice_lock/record_lock_bottom", storiesComposeBg }};
originBody: icon {{ "voice_lock/record_lock_body", storiesComposeBg }};
arrow: icon {{ "voice_lock/voice_arrow", storiesComposeGrayIcon }};
fg: storiesComposeGrayIcon;
}
remove: IconButton(storiesAttach) {
icon: icon {{ "info/info_media_delete", storiesComposeGrayIcon }};
iconOver: icon {{ "info/info_media_delete", storiesComposeGrayIcon }};
iconPosition: point(10px, 11px);
}
}
} }