From a29e93ca55d40c3b50d654fd2a9a153ab0227b6c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 12:35:53 +0300 Subject: [PATCH] Added initial implementation of label animation in mute button. --- ui/widgets/call_mute_button.cpp | 165 ++++++++++++++++++++++++++++---- ui/widgets/call_mute_button.h | 9 +- ui/widgets/widgets.style | 2 + 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 9589ec8..15e3820 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -47,6 +47,7 @@ constexpr auto kOverrideColorRippleAlpha = 50; constexpr auto kShiftDuration = crl::time(300); constexpr auto kSwitchStateDuration = crl::time(120); +constexpr auto kSwitchLabelDuration = crl::time(180); // Switch state from Connecting animation. constexpr auto kSwitchRadialDuration = crl::time(350); @@ -148,6 +149,110 @@ void ComputeRadialFinish( } // namespace +class AnimatedLabel final : public RpWidget { +public: + AnimatedLabel( + QWidget *parent, + rpl::producer &&text, + crl::time duration, + int additionalHeight, + const style::FlatLabel &st = st::defaultFlatLabel); + + int height() const; + +private: + int realHeight() const; + + void setText(const QString &text); + + const style::FlatLabel &_st; + const crl::time _duration; + const int _additionalHeight; + const TextParseOptions _options; + + Text::String _text; + Text::String _previousText; + + Animations::Simple _animation; + +}; + +AnimatedLabel::AnimatedLabel( + QWidget *parent, + rpl::producer &&text, + crl::time duration, + int additionalHeight, + const style::FlatLabel &st) +: RpWidget(parent) +, _st(st) +, _duration(duration) +, _additionalHeight(additionalHeight) +, _options({ 0, 0, 0, Qt::LayoutDirectionAuto }) { + std::move( + text + ) | rpl::start_with_next([=](const QString &value) { + setText(value); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + const auto progress = _animation.value(1.); + + p.setFont(_st.style.font); + p.setPen(_st.textFg); + p.setTextPalette(_st.palette); + + const auto textHeight = height(); + const auto diffHeight = realHeight() - textHeight; + const auto center = (diffHeight) / 2; + + p.setOpacity(1. - progress); + if (p.opacity()) { + _previousText.draw( + p, + 0, + anim::interpolate(center, diffHeight, progress), + width(), + style::al_center); + } + + p.setOpacity(progress); + if (p.opacity()) { + _text.draw( + p, + 0, + anim::interpolate(0, center, progress), + width(), + style::al_center); + } + }, lifetime()); +} + +int AnimatedLabel::height() const { + return _st.style.font->height; +} + +int AnimatedLabel::realHeight() const { + return RpWidget::height(); +} + +void AnimatedLabel::setText(const QString &text) { + if (_text.toString() == text) { + return; + } + _previousText = _text; + _text.setText(_st.style, text, _options); + + const auto width = std::max( + _st.style.font->width(_text.toString()), + _st.style.font->width(_previousText.toString())); + resize(width + _additionalHeight, height() + _additionalHeight * 2); + + _animation.stop(); + _animation.start([=] { update(); }, 0., 1., _duration); +} + class BlobsWidget final : public RpWidget { public: BlobsWidget( @@ -379,19 +484,32 @@ CallMuteButton::CallMuteButton( return isBadState || !(!animDisabled && !hide); }))) , _content(base::make_unique_q(parent)) -, _label(base::make_unique_q( +, _centerLabel(base::make_unique_q( parent, _state.value( ) | rpl::map([](const CallMuteButtonState &state) { - return state.text; + return state.subtext.isEmpty() ? state.text : QString(); }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, _st.label)) -, _sublabel(base::make_unique_q( +, _label(base::make_unique_q( + parent, + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext.isEmpty() ? QString() : state.text; + }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, + _st.label)) +, _sublabel(base::make_unique_q( parent, _state.value( ) | rpl::map([](const CallMuteButtonState &state) { return state.subtext; }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, st::callMuteButtonSublabel)) , _radial(nullptr) , _colors(Colors()) @@ -411,10 +529,9 @@ void CallMuteButton::init() { _label->show(); rpl::combine( _content->geometryValue(), - _sublabel->widthValue(), _label->sizeValue() - ) | rpl::start_with_next([=](QRect my, int subWidth, QSize size) { - updateLabelGeometry(my, subWidth, size); + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateLabelGeometry(my, size); }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -427,6 +544,15 @@ void CallMuteButton::init() { }, _sublabel->lifetime()); _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); + _centerLabel->show(); + rpl::combine( + _content->geometryValue(), + _centerLabel->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateCenterLabelGeometry(my, size); + }, _centerLabel->lifetime()); + _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + _radialInfo.rawShowProgress.value( ) | rpl::start_with_next([=](float64 value) { auto &info = _radialInfo; @@ -626,27 +752,34 @@ void CallMuteButton::init() { } void CallMuteButton::updateLabelsGeometry() { - updateLabelGeometry( - _content->geometry(), - _sublabel->width(), - _label->size()); + updateLabelGeometry(_content->geometry(), _label->size()); + updateCenterLabelGeometry(_content->geometry(), _centerLabel->size()); updateSublabelGeometry(_content->geometry(), _sublabel->size()); } -void CallMuteButton::updateLabelGeometry(QRect my, int subWidth, QSize size) { - const auto skip = subWidth - ? st::callMuteButtonSublabelSkip - : (st::callMuteButtonSublabelSkip / 2); +void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { + const auto skip = st::callMuteButtonSublabelSkip + + st::callMuteButtonLabelsSkip; _label->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - skip, + my.y() + my.height() - _label->height() - skip, + my.width()); +} + +void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) { + const auto skip = (st::callMuteButtonSublabelSkip / 2) + + st::callMuteButtonLabelsSkip; + _centerLabel->moveToLeft( + my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, + my.y() + my.height() - _centerLabel->height() - skip, my.width()); } void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) { + const auto skip = st::callMuteButtonLabelsSkip; _sublabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height(), + my.y() + my.height() - _sublabel->height() - skip, my.width()); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 0a799cf..154bfb3 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -19,6 +19,7 @@ class BlobsWidget; class AbstractButton; class FlatLabel; class RpWidget; +class AnimatedLabel; struct CallButtonColors; @@ -87,7 +88,8 @@ private: float64 progress); void setHandleMouseState(HandleMouseState state); - void updateLabelGeometry(QRect my, int subWidth, QSize size); + void updateCenterLabelGeometry(QRect my, QSize size); + void updateLabelGeometry(QRect my, QSize size); void updateSublabelGeometry(QRect my, QSize size); void updateLabelsGeometry(); @@ -104,8 +106,9 @@ private: const base::unique_qptr _blobs; const base::unique_qptr _content; - const base::unique_qptr _label; - const base::unique_qptr _sublabel; + const base::unique_qptr _centerLabel; + const base::unique_qptr _label; + const base::unique_qptr _sublabel; int _labelShakeShift = 0; RadialInfo _radialInfo; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 289f290..ced097c 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1433,6 +1433,7 @@ callMuteButtonLabel: FlatLabel(defaultFlatLabel) { callMuteButtonSublabel: FlatLabel(defaultFlatLabel) { textFg: groupCallMemberNotJoinedStatus; } +callMuteButtonLabelsSkip: 5px; callMuteButtonSublabelSkip: 19px; callMuteButtonActive: CallButton { button: callMuteButtonActiveInner; @@ -1457,6 +1458,7 @@ callMuteButtonConnecting: CallButton(callMuteButtonMuted) { bg: callIconBg; label: callMuteButtonLabel; } +callMuteButtonLabelAdditional: 5px; callMuteCrossLine: CrossLineAnimation { fg: groupCallIconFg;