diff --git a/Telegram/Resources/icons/chat/voice_to_text.png b/Telegram/Resources/icons/chat/voice_to_text.png new file mode 100644 index 000000000..b677a7c5e Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text.png differ diff --git a/Telegram/Resources/icons/chat/voice_to_text@2x.png b/Telegram/Resources/icons/chat/voice_to_text@2x.png new file mode 100644 index 000000000..224b40bb3 Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text@2x.png differ diff --git a/Telegram/Resources/icons/chat/voice_to_text@3x.png b/Telegram/Resources/icons/chat/voice_to_text@3x.png new file mode 100644 index 000000000..731ba5aad Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text@3x.png differ diff --git a/Telegram/Resources/icons/chat/voice_to_text_collapse.png b/Telegram/Resources/icons/chat/voice_to_text_collapse.png new file mode 100644 index 000000000..752f8be2f Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text_collapse.png differ diff --git a/Telegram/Resources/icons/chat/voice_to_text_collapse@2x.png b/Telegram/Resources/icons/chat/voice_to_text_collapse@2x.png new file mode 100644 index 000000000..da161b733 Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text_collapse@2x.png differ diff --git a/Telegram/Resources/icons/chat/voice_to_text_collapse@3x.png b/Telegram/Resources/icons/chat/voice_to_text_collapse@3x.png new file mode 100644 index 000000000..204ffcbac Binary files /dev/null and b/Telegram/Resources/icons/chat/voice_to_text_collapse@3x.png differ diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index 5284f43c2..217563dc4 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -13,18 +13,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/click_handler.h" +#include "ui/effects/radial_animation.h" #include "api/api_transcribes.h" #include "apiwrap.h" #include "styles/style_chat.h" namespace HistoryView { +namespace { + +constexpr auto kInNonChosenOpacity = 0.12; +constexpr auto kOutNonChosenOpacity = 0.18; + +} // namespace TranscribeButton::TranscribeButton(not_null item) : _item(item) { } +TranscribeButton::~TranscribeButton() = default; + QSize TranscribeButton::size() const { - return QSize(st::historyTranscribeSize, st::historyTranscribeSize); + return st::historyTranscribeSize; +} + +void TranscribeButton::setLoading(bool loading, Fn update) { + if (_loading == loading) { + return; + } + _loading = loading; + if (_loading) { + _animation = std::make_unique( + update, + st::defaultInfiniteRadialAnimation); + _animation->start(); + } else if (_animation) { + _animation->stop(); + } } void TranscribeButton::paint( @@ -33,10 +57,80 @@ void TranscribeButton::paint( int y, const PaintContext &context) { auto hq = PainterHighQualityEnabler(p); + const auto opened = _openedAnimation.value(_opened ? 1. : 0.); const auto stm = context.messageStyle(); - p.setBrush(stm->msgWaveformInactive); - const auto radius = size().width() / 4; - p.drawRoundedRect(QRect{ QPoint(x, y), size() }, radius, radius); + auto bg = stm->msgFileBg->c; + bg.setAlphaF(bg.alphaF() * (context.outbg + ? kOutNonChosenOpacity + : kInNonChosenOpacity)); + p.setBrush(bg); + const auto radius = st::historyTranscribeRadius; + const auto state = _animation + ? _animation->computeState() + : Ui::RadialState(); + if (state.shown > 0.) { + auto fg = stm->msgWaveformActive->c; + fg.setAlphaF(fg.alphaF() * state.shown * (1. - opened)); + auto pen = QPen(fg); + const auto thickness = style::ConvertScaleExact(2.); + const auto widthNoRadius = size().width() - 2 * radius; + const auto heightNoRadius = size().height() - 2 * radius; + const auto length = 2 * (widthNoRadius + heightNoRadius) + + 2 * M_PI * radius; + pen.setWidthF(thickness); + pen.setCapStyle(Qt::RoundCap); + const auto ratio = length / (Ui::RadialState::kFull * thickness); + const auto filled = ratio * state.arcLength; + pen.setDashPattern({ filled, (length / thickness) - filled }); + pen.setDashOffset(ratio * (state.arcFrom + state.arcLength)); + p.setPen(pen); + } else { + p.setPen(Qt::NoPen); + if (!_loading) { + _animation = nullptr; + } + } + const auto r = QRect{ QPoint(x, y), size() }; + p.drawRoundedRect(r, radius, radius); + if (opened > 0.) { + if (opened != 1.) { + p.save(); + p.setOpacity(opened); + p.translate(r.center()); + p.scale(opened, opened); + p.translate(-r.center()); + } + stm->historyTranscribeHide.paintInCenter(p, r); + if (opened != 1.) { + p.restore(); + } + } + if (opened < 1.) { + if (opened != 0.) { + p.save(); + p.setOpacity(1. - opened); + p.translate(r.center()); + p.scale(1. - opened, 1. - opened); + p.translate(-r.center()); + } + stm->historyTranscribeIcon.paintInCenter(p, r); + if (opened != 0.) { + p.restore(); + } + } + p.setOpacity(1.); +} + +void TranscribeButton::setOpened(bool opened, Fn update) { + if (_opened == opened) { + return; + } + _opened = opened; + _openedAnimation.start( + std::move(update), + _opened ? 0. : 1., + _opened ? 1. : 0., + st::fadeWrapDuration); } ClickHandlerPtr TranscribeButton::link() { diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h index de506cde0..69ee34413 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h @@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "ui/effects/animations.h" + namespace Ui { struct ChatPaintContext; +class InfiniteRadialAnimation; } // namespace Ui namespace HistoryView { @@ -18,9 +21,12 @@ using PaintContext = Ui::ChatPaintContext; class TranscribeButton final { public: explicit TranscribeButton(not_null item); + ~TranscribeButton(); [[nodiscard]] QSize size() const; + void setOpened(bool opened, Fn update); + void setLoading(bool loading, Fn update); void paint(QPainter &p, int x, int y, const PaintContext &context); [[nodiscard]] ClickHandlerPtr link(); @@ -28,10 +34,12 @@ public: private: const not_null _item; + mutable std::unique_ptr _animation; ClickHandlerPtr _link; QString _text; - bool _loaded = false; + Ui::Animations::Simple _openedAnimation; bool _loading = false; + bool _opened = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 5181676fd..ec8e9b6e6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/text/format_song_document_name.h" +#include "ui/text/text_utilities.h" #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" #include "ui/cached_round_corners.h" @@ -105,6 +106,7 @@ void PaintWaveform( const auto maxDelta = st::msgWaveformMax - st::msgWaveformMin; const auto &bottom = st::msgWaveformMax; p.setPen(Qt::NoPen); + auto hq = PainterHighQualityEnabler(p); for (auto i = 0, barLeft = 0, sum = 0, maxValue = 0; i < wfSize; ++i) { const auto value = wf ? wf->at(i) : 0; if (sum + barCount < wfSize) { @@ -120,16 +122,20 @@ void PaintWaveform( const auto barValue = ((maxValue * maxDelta) + (barNormValue / 2)) / barNormValue; const auto barHeight = st::msgWaveformMin + barValue; - const auto barTop = bottom - barValue; + const auto barTop = st::lineWidth + (st::msgWaveformMax - barValue) / 2.; if ((barLeft < activeWidth) && (barLeft + barWidth > activeWidth)) { const auto leftWidth = activeWidth - barLeft; const auto rightWidth = barWidth - leftWidth; - p.fillRect(barLeft, barTop, leftWidth, barHeight, active); - p.fillRect(activeWidth, barTop, rightWidth, barHeight, inactive); + p.fillRect( + QRectF(barLeft, barTop, leftWidth, barHeight), + active); + p.fillRect( + QRectF(activeWidth, barTop, rightWidth, barHeight), + inactive); } else { const auto &color = (barLeft >= activeWidth) ? inactive : active; - p.fillRect(barLeft, barTop, barWidth, barHeight, color); + p.fillRect(QRectF(barLeft, barTop, barWidth, barHeight), color); } barLeft += barWidth + st::msgWaveformSkip; @@ -244,22 +250,26 @@ QSize Document::countOptimalSize() { } const auto &entry = session->api().transcribes().entry( _realParent); - auto text = entry.requestId - ? "Transcribing..." + const auto update = [=] { repaint(); }; + voice->transcribe->setLoading( + entry.shown && (entry.requestId || entry.pending), + update); + auto text = (entry.requestId || !entry.shown) + ? TextWithEntities() : entry.failed - ? "Transcribing Failed." - : entry.shown - ? ((entry.pending ? "Still Transcribing...\n" : "") - + entry.result) - : QString(); - if (text.isEmpty()) { + ? Ui::Text::Italic(tr::lng_attach_failed(tr::now)) + : TextWithEntities{ entry.result }; + voice->transcribe->setOpened(!text.empty(), update); + if (text.empty()) { voice->transcribeText = {}; } else { const auto minResizeWidth = st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right(); voice->transcribeText = Ui::Text::String(minResizeWidth); - voice->transcribeText.setText(st::messageTextStyle, text); + voice->transcribeText.setMarkedText( + st::messageTextStyle, + text); hasTranscribe = true; } } @@ -599,10 +609,7 @@ void Document::draw( const auto size = voice->transcribe->size(); namewidth -= st::historyTranscribeSkip + size.width(); const auto x = nameleft + namewidth + st::historyTranscribeSkip; - const auto y = st.padding.top() - - topMinus - + st::msgWaveformMax - - size.height(); + const auto y = st.padding.top() - topMinus; voice->transcribe->paint(p, x, y, context); } p.save(); @@ -837,10 +844,7 @@ TextState Document::textState( const auto size = voice->transcribe->size(); namewidth -= st::historyTranscribeSkip + size.width(); const auto x = nameleft + namewidth + st::historyTranscribeSkip; - const auto y = st.padding.top() - - topMinus - + st::msgWaveformMax - - size.height(); + const auto y = st.padding.top() - topMinus; if (QRect(QPoint(x, y), size).contains(point)) { result.link = voice->transcribe->link(); return result; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b623141ca..5e766f47b 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -652,11 +652,20 @@ msgVideoSize: size(320px, 240px); msgWaveformBar: 2px; msgWaveformSkip: 1px; -msgWaveformMin: 2px; -msgWaveformMax: 20px; +msgWaveformMin: 3px; +msgWaveformMax: 17px; historyTranscribeSkip: 10px; -historyTranscribeSize: 24px; +historyTranscribeSize: size(28px, 22px); +historyTranscribeRadius: 4px; +historyTranscribeInIcon: icon {{ "chat/voice_to_text", msgFileInBg }}; +historyTranscribeInIconSelected: icon {{ "chat/voice_to_text", msgFileInBgSelected }}; +historyTranscribeOutIcon: icon {{ "chat/voice_to_text", msgFileOutBg }}; +historyTranscribeOutIconSelected: icon {{ "chat/voice_to_text", msgFileOutBgSelected }}; +historyTranscribeInHide: icon {{ "chat/voice_to_text_collapse", msgFileInBg }}; +historyTranscribeInHideSelected: icon {{ "chat/voice_to_text_collapse", msgFileInBgSelected }}; +historyTranscribeOutHide: icon {{ "chat/voice_to_text_collapse", msgFileOutBg }}; +historyTranscribeOutHideSelected: icon {{ "chat/voice_to_text_collapse", msgFileOutBgSelected }}; historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }}; historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }}; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 5c2a9c270..2328f2182 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -398,6 +398,18 @@ ChatStyle::ChatStyle() { st::historyPollInChoiceRightSelected, st::historyPollOutChoiceRight, st::historyPollOutChoiceRightSelected); + make( + &MessageStyle::historyTranscribeIcon, + st::historyTranscribeInIcon, + st::historyTranscribeInIconSelected, + st::historyTranscribeOutIcon, + st::historyTranscribeOutIconSelected); + make( + &MessageStyle::historyTranscribeHide, + st::historyTranscribeInHide, + st::historyTranscribeInHideSelected, + st::historyTranscribeOutHide, + st::historyTranscribeOutHideSelected); make( &MessageImageStyle::msgDateImgBg, st::msgDateImgBg, diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index e9e176542..6a1f34fee 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -70,6 +70,9 @@ struct MessageStyle { style::icon historyQuizExplain = { Qt::Uninitialized }; style::icon historyPollChosen = { Qt::Uninitialized }; style::icon historyPollChoiceRight = { Qt::Uninitialized }; + style::icon historyTranscribeIcon = { Qt::Uninitialized }; + style::icon historyTranscribeHide = { Qt::Uninitialized }; + }; struct MessageImageStyle {