diff --git a/Telegram/Resources/animations/chat/video_to_voice.tgs b/Telegram/Resources/animations/chat/video_to_voice.tgs
new file mode 100644
index 0000000000..854a6c734c
Binary files /dev/null and b/Telegram/Resources/animations/chat/video_to_voice.tgs differ
diff --git a/Telegram/Resources/animations/chat/voice_to_video.tgs b/Telegram/Resources/animations/chat/voice_to_video.tgs
new file mode 100644
index 0000000000..de864f07d9
Binary files /dev/null and b/Telegram/Resources/animations/chat/voice_to_video.tgs differ
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index a33a9ff016..3902bfff8b 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -17,6 +17,8 @@
../../animations/stats_earn.tgs
../../animations/voice_ttl_idle.tgs
../../animations/voice_ttl_start.tgs
+ ../../animations/chat/voice_to_video.tgs
+ ../../animations/chat/video_to_voice.tgs
../../animations/palette.tgs
../../animations/sleep.tgs
../../animations/greeting.tgs
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 08b4c44f3f..e1b1db8b3b 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -153,10 +153,7 @@ SendButton {
sendIconFillPadding: pixels;
inner: IconButton;
stars: RoundButton;
- record: icon;
- recordOver: icon;
- round: icon;
- roundOver: icon;
+ recordSize: size;
sendDisabledFg: color;
}
@@ -1287,18 +1284,11 @@ historyRecordVoiceFgOver: historyComposeIconFgOver;
historyRecordVoiceFgInactive: attentionButtonFg;
historyRecordVoiceFgActive: windowBgActive;
historyRecordVoiceFgActiveIcon: windowFgActive;
-historyRecordVoice: icon {{ "chat/input_record", historyRecordVoiceFg }};
-historyRecordVoiceOver: icon {{ "chat/input_record", historyRecordVoiceFgOver }};
historyRecordVoiceOnceBg: icon {{ "voice_lock/audio_once_bg", historySendIconFg }};
historyRecordVoiceOnceBgOver: icon {{ "voice_lock/audio_once_bg", historySendIconFgOver }};
historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive }};
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
-historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
-historyRecordRound: icon {{ "chat/input_video", historyRecordVoiceFg }};
-historyRecordRoundOver: icon {{ "chat/input_video", historyRecordVoiceFgOver }};
-historyRecordRoundActive: icon {{ "chat/input_video", historyRecordVoiceFgActiveIcon }};
-historyRecordRoundIconPosition: point(0px, 0px);
historyRecordSendIconPosition: point(2px, 0px);
historyRecordVoiceRippleBgActive: lightButtonBgOver;
historyRecordSignalRadius: 5px;
@@ -1411,12 +1401,10 @@ historySend: SendButton {
textTop: 5px;
width: -8px;
}
- record: historyRecordVoice;
- recordOver: historyRecordVoiceOver;
- round: historyRecordRound;
- roundOver: historyRecordRoundOver;
+ recordSize: size(26px, 26px);
sendDisabledFg: historyComposeIconFg;
}
+historyRecordFrameIndex: 30;
defaultComposeFilesMenu: IconButton(defaultIconButton) {
width: 48px;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp
index b796d27858..abca0b803a 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/controls/history_view_voice_record_button.h"
+#include "lottie/lottie_icon.h"
#include "ui/paint/blobs.h"
#include "ui/painter.h"
#include "styles/style_chat.h"
@@ -24,6 +25,8 @@ constexpr auto kBlobAlpha = 76. / 255.;
constexpr auto kBlobMaxSpeed = 5.0;
constexpr auto kLevelDuration = 100. + 500. * 0.33;
constexpr auto kBlobsScaleEnterDuration = crl::time(250);
+constexpr auto kVoiceIconIndex = 0;
+constexpr auto kRoundIconIndex = 1;
auto Blobs() {
return std::vector{
@@ -135,22 +138,28 @@ void VoiceRecordButton::init() {
p.scale(scale, scale);
}
const auto state = *currentState;
- const auto icon = (state == Type::Send)
- ? st::historySendIcon
- : (state == Type::Record)
- ? st::historyRecordVoiceActive
- : st::historyRecordRoundActive;
- const auto position = (state == Type::Send)
- ? st::historyRecordSendIconPosition
- : (state == Type::Record)
- ? QPoint(0, 0)
- : st::historyRecordRoundIconPosition;
- icon.paint(
- p,
- -icon.width() / 2 + position.x(),
- -icon.height() / 2 + position.y(),
- 0,
- st::historyRecordVoiceFgActiveIcon->c);
+ if (state == Type::Send) {
+ const auto icon = st::historySendIcon;
+ const auto position = st::historyRecordSendIconPosition;
+ icon.paint(
+ p,
+ -icon.width() / 2 + position.x(),
+ -icon.height() / 2 + position.y(),
+ 0,
+ st::historyRecordVoiceFgActiveIcon->c);
+ } else {
+ const auto index = (state == Type::Record)
+ ? kVoiceIconIndex
+ : kRoundIconIndex;
+ auto &icon = _voiceRoundIcons[index];
+ if (!icon) {
+ initVoiceRoundIcon(index);
+ }
+ icon->paintInCenter(
+ p,
+ rect().translated(-_center, -_center),
+ st::historyRecordVoiceFgActiveIcon->c);
+ }
}
}, lifetime());
@@ -205,6 +214,18 @@ void VoiceRecordButton::init() {
}, lifetime());
}
+void VoiceRecordButton::initVoiceRoundIcon(int index) {
+ Expects(index >= 0 && index < 2);
+
+ _voiceRoundIcons[index] = Lottie::MakeIcon({
+ .path = ((index == kVoiceIconIndex)
+ ? u":/animations/chat/voice_to_video.tgs"_q
+ : u":/animations/chat/video_to_voice.tgs"_q),
+ .sizeOverride = st::historySend.recordSize,
+ .colorizeUsingAlpha = true,
+ });
+}
+
rpl::producer VoiceRecordButton::actives() const {
return events(
) | rpl::filter([=](not_null e) {
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h
index e8e2dd7b65..4465a95925 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h
@@ -15,6 +15,10 @@ namespace style {
struct RecordBar;
} // namespace style
+namespace Lottie {
+class Icon;
+} // namespace Lottie
+
namespace Ui::Paint {
class Blobs;
} // namespace Ui::Paint
@@ -47,8 +51,10 @@ public:
private:
void init();
+ void initVoiceRoundIcon(int index);
std::unique_ptr _blobs;
+ std::array, 2> _voiceRoundIcons;
crl::time _lastUpdateTime = 0;
crl::time _blobsHideLastTime = 0;
diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style
index fa3d245c58..442277256c 100644
--- a/Telegram/SourceFiles/media/view/media_view.style
+++ b/Telegram/SourceFiles/media/view/media_view.style
@@ -493,10 +493,6 @@ storiesLike: IconButton(storiesAttach) {
icon: icon {{ "chat/input_like", storiesComposeGrayIcon }};
iconOver: icon {{ "chat/input_like", storiesComposeGrayIcon }};
}
-storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }};
-storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }};
-storiesRecordRound: icon {{ "chat/input_video", storiesComposeGrayIcon }};
-storiesRecordRoundOver: icon {{ "chat/input_video", storiesComposeGrayIcon }};
storiesEditStars: IconButton(storiesAttach) {
icon: icon{{ "chat/input_paid", storiesComposeGrayIcon }};
iconOver: icon{{ "chat/input_paid", storiesComposeGrayIcon }};
@@ -727,10 +723,6 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
rippleAreaSize: 40px;
rippleAreaPosition: point(1px, 1px);
}
- record: storiesRecordVoice;
- recordOver: storiesRecordVoiceOver;
- round: storiesRecordRound;
- roundOver: storiesRecordRoundOver;
sendDisabledFg: storiesComposeGrayText;
}
attach: storiesAttach;
diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp
index 7cf772cc2d..0dd88a0f57 100644
--- a/Telegram/SourceFiles/ui/controls/send_button.cpp
+++ b/Telegram/SourceFiles/ui/controls/send_button.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/send_button.h"
#include "lang/lang_tag.h"
+#include "lottie/lottie_icon.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
@@ -19,7 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
namespace {
-constexpr int kWideScale = 5;
+constexpr auto kWideScale = 5;
+constexpr auto kVoiceToRoundIndex = 0;
+constexpr auto kRoundToVoiceIndex = 1;
} // namespace
@@ -29,19 +32,34 @@ SendButton::SendButton(QWidget *parent, const style::SendButton &st)
updateSize();
}
+SendButton::~SendButton() = default;
+
void SendButton::setState(State state) {
if (_state == state) {
return;
}
+
+ const auto previousType = _state.type;
+ const auto newType = state.type;
+ const auto voiceRoundTransition = isVoiceRoundTransition(
+ previousType,
+ newType);
+
const auto hasSlowmode = (_state.slowmodeDelay > 0);
const auto hasSlowmodeChanged = hasSlowmode != (state.slowmodeDelay > 0);
auto withSameSlowmode = state;
withSameSlowmode.slowmodeDelay = _state.slowmodeDelay;
const auto animate = hasSlowmodeChanged
|| (!hasSlowmode && withSameSlowmode != _state);
- if (animate) {
+
+ if (animate && !voiceRoundTransition) {
_contentFrom = grabContent();
}
+
+ if (_voiceRoundAnimating && !voiceRoundTransition) {
+ _voiceRoundAnimating = false;
+ }
+
if (_state.slowmodeDelay != state.slowmodeDelay) {
const auto seconds = state.slowmodeDelay;
const auto minutes = seconds / 60;
@@ -60,7 +78,29 @@ void SendButton::setState(State state) {
kMarkupTextOptions);
}
_state = state;
- if (animate) {
+
+ if (voiceRoundTransition) {
+ _voiceRoundAnimating = true;
+
+ const auto toRound = (newType == Type::Round);
+ const auto index = toRound ? kVoiceToRoundIndex : kRoundToVoiceIndex;
+ auto &icon = _voiceRoundIcons[index];
+ if (!icon) {
+ initVoiceRoundIcon(index);
+ }
+ icon->animate([=, raw = icon.get()] {
+ update();
+ if (!raw->animating()) {
+ _voiceRoundAnimating = false;
+ }
+ }, 0, icon->framesCount() - 1);
+ auto &after = _voiceRoundIcons[1 - index];
+ if (!after) {
+ initVoiceRoundIcon(1 - index);
+ } else if (after->frameIndex() != 0) {
+ after->jumpTo(0, nullptr);
+ }
+ } else if (animate) {
_stateChangeFromWidth = width();
_stateChangeAnimation.stop();
updateSize();
@@ -86,6 +126,12 @@ void SendButton::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
auto over = (isDown() || isOver());
+
+ if (_voiceRoundAnimating) {
+ paintVoiceRoundIcon(p, over);
+ return;
+ }
+
auto changed = _stateChangeAnimation.value(1.);
if (changed < 1.) {
PainterHighQualityEnabler hq(p);
@@ -153,11 +199,7 @@ void SendButton::paintRecord(QPainter &p, bool over) {
(width() - _st.inner.rippleAreaSize) / 2,
_st.inner.rippleAreaPosition.y());
}
-
- const auto &icon = (isDisabled() || !over)
- ? _st.record
- : _st.recordOver;
- icon.paintInCenter(p, rect());
+ paintLottieIcon(p, kVoiceToRoundIndex, over);
}
void SendButton::paintRound(QPainter &p, bool over) {
@@ -167,11 +209,20 @@ void SendButton::paintRound(QPainter &p, bool over) {
(width() - _st.inner.rippleAreaSize) / 2,
_st.inner.rippleAreaPosition.y());
}
+ paintLottieIcon(p, kRoundToVoiceIndex, over);
+}
- const auto &icon = (isDisabled() || !over)
- ? _st.round
- : _st.roundOver;
- icon.paintInCenter(p, rect());
+void SendButton::paintLottieIcon(QPainter &p, int index, bool over) {
+ auto &icon = _voiceRoundIcons[index];
+ if (!icon) {
+ initVoiceRoundIcon(index);
+ } else if (!_voiceRoundAnimating && icon->frameIndex() != 0) {
+ icon->jumpTo(0, [=] { update(); });
+ }
+ const auto color = (isDisabled() || !over)
+ ? st::historyRecordVoiceFg->c
+ : st::historyRecordVoiceFgOver->c;
+ icon->paintInCenter(p, rect(), color);
}
void SendButton::paintSave(QPainter &p, bool over) {
@@ -330,6 +381,39 @@ QPoint SendButton::prepareRippleStartPosition() const {
return real - QPoint((width() - size) / 2, y);
}
+void SendButton::initVoiceRoundIcon(int index) {
+ Expects(index >= 0 && index < 2);
+
+ _voiceRoundIcons[index] = Lottie::MakeIcon({
+ .path = ((index == kVoiceToRoundIndex)
+ ? u":/animations/chat/voice_to_video.tgs"_q
+ : u":/animations/chat/video_to_voice.tgs"_q),
+ .sizeOverride = _st.recordSize,
+ .colorizeUsingAlpha = true,
+ });
+}
+
+void SendButton::paintVoiceRoundIcon(QPainter &p, bool over) {
+ if (!isDisabled()) {
+ paintRipple(
+ p,
+ (width() - _st.inner.rippleAreaSize) / 2,
+ _st.inner.rippleAreaPosition.y());
+ }
+
+ const auto color = (isDisabled() || !over)
+ ? st::historyRecordVoiceFg->c
+ : st::historyRecordVoiceFgOver->c;
+ const auto toVideo = (_state.type == Type::Round);
+ const auto index = toVideo ? kVoiceToRoundIndex : kRoundToVoiceIndex;
+ _voiceRoundIcons[index]->paintInCenter(p, rect(), color);
+}
+
+bool SendButton::isVoiceRoundTransition(Type from, Type to) {
+ return (from == Type::Record && to == Type::Round)
+ || (from == Type::Round && to == Type::Record);
+}
+
SendStarButton::SendStarButton(
QWidget *parent,
const style::IconButton &st,
diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h
index cd9fe21b72..04529b9b95 100644
--- a/Telegram/SourceFiles/ui/controls/send_button.h
+++ b/Telegram/SourceFiles/ui/controls/send_button.h
@@ -9,6 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
+#include
+
+namespace Lottie {
+class Icon;
+} // namespace Lottie
+
namespace style {
struct SendButton;
struct IconButton;
@@ -20,6 +26,7 @@ namespace Ui {
class SendButton final : public RippleButton {
public:
SendButton(QWidget *parent, const style::SendButton &st);
+ ~SendButton();
static constexpr auto kSlowmodeDelayLimit = 100 * 60;
@@ -76,6 +83,11 @@ private:
void paintSlowmode(QPainter &p);
void paintStarsToSend(QPainter &p, bool over);
+ void initVoiceRoundIcon(int index);
+ void paintVoiceRoundIcon(QPainter &p, bool over);
+ [[nodiscard]] static bool isVoiceRoundTransition(Type from, Type to);
+ void paintLottieIcon(QPainter &p, int index, bool over);
+
const style::SendButton &_st;
State _state;
@@ -87,6 +99,9 @@ private:
QString _slowmodeDelayText;
Ui::Text::String _starsToSendText;
+ std::array, 2> _voiceRoundIcons;
+ bool _voiceRoundAnimating = false;
+
};
struct SendStarButtonState {
diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
index 9b05d2fafd..0130e318a0 100644
--- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_three_state_icon.h"
#include "lang/lang_keys.h"
+#include "lottie/lottie_icon.h"
#include "platform/platform_window_title.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
@@ -565,7 +566,13 @@ void Generator::paintComposeArea() {
: st::historyAttach.iconPosition.y();
st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + attachIconLeft, controlsTop + attachIconTop, _rect.width());
auto right = st::historySendRight + st::historySendSize.width();
- st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height()));
+
+ const auto recordIcon = Lottie::MakeIcon({
+ .path = u":/animations/chat/voice_to_video.tgs"_q,
+ .sizeOverride = st::historySend.recordSize,
+ .colorizeUsingAlpha = true,
+ });
+ recordIcon->paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height()), st::historyRecordVoiceFg[_palette]->c);
const auto &emojiButton = st::historyAttachEmoji.inner;
const auto emojiIconLeft = (emojiButton.iconPosition.x() < 0)
diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie
index 99d8cc4d18..cba5d113eb 160000
--- a/Telegram/lib_lottie
+++ b/Telegram/lib_lottie
@@ -1 +1 @@
-Subproject commit 99d8cc4d1820960fbcdafc574f1894073b14b75a
+Subproject commit cba5d113eb941c3eec20c4d171ff4ff9d1a92382