From 2214e980efa9815a2bf4e2aba3239d8c8ef42dda Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 4 May 2017 16:32:56 +0300 Subject: [PATCH] Add call panel show / hide animation. --- Telegram/SourceFiles/calls/calls.style | 1 + Telegram/SourceFiles/calls/calls_call.cpp | 13 +--- Telegram/SourceFiles/calls/calls_instance.cpp | 18 ++++- Telegram/SourceFiles/calls/calls_instance.h | 2 + Telegram/SourceFiles/calls/calls_panel.cpp | 73 +++++++++++++++++-- Telegram/SourceFiles/calls/calls_panel.h | 10 ++- 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 07ba42007e..347d39395c 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -188,3 +188,4 @@ callRatingCommentTop: 2px; callDebugLabel: FlatLabel(defaultFlatLabel) { margin: margins(24px, 0px, 24px, 0px); } +callPanelDuration: 200; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 7416a426b0..f17f378dab 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -203,8 +203,7 @@ TimeMs Call::getDurationMs() const { void Call::hangup() { if (_state == State::Busy) { - // Cancel call instead of redial. - setState(Call::Ended); + _delegate->callFinished(this); } else { auto missed = (_state == State::Ringing || (_state == State::Waiting && _type == Type::Outgoing)); auto declined = (_state == State::WaitingIncoming); @@ -513,8 +512,6 @@ bool Call::checkCallFields(const MTPDphoneCallAccepted &call) { void Call::setState(State state) { if (_state != state) { - auto wasBusy = (_state == State::Busy); - _state = state; _stateChanged.notify(state, true); @@ -534,15 +531,11 @@ void Call::setState(State state) { _delegate->playSound(Delegate::Sound::Connecting); break; case State::Ended: - if (!wasBusy) { - _delegate->playSound(Delegate::Sound::Ended); - } + _delegate->playSound(Delegate::Sound::Ended); _delegate->callFinished(this); break; case State::Failed: - if (!wasBusy) { - _delegate->playSound(Delegate::Sound::Ended); - } + _delegate->playSound(Delegate::Sound::Ended); _delegate->callFailed(this); break; case State::Busy: diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 8a687119c2..da4fc6f79f 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -98,7 +98,7 @@ void Instance::playSound(Sound sound) { void Instance::destroyCall(gsl::not_null call) { if (_currentCall.get() == call) { - _currentCallPanel.reset(); + destroyCurrentPanel(); _currentCall.reset(); _currentCallChanged.notify(nullptr, true); @@ -109,6 +109,14 @@ void Instance::destroyCall(gsl::not_null call) { } } +void Instance::destroyCurrentPanel() { + _pendingPanels.erase(std::remove_if(_pendingPanels.begin(), _pendingPanels.end(), [](auto &&panel) { + return !panel; + }), _pendingPanels.end()); + _pendingPanels.push_back(_currentCallPanel.release()); + _pendingPanels.back()->hideAndDestroy(); // Always queues the destruction. +} + void Instance::createCall(gsl::not_null user, Call::Type type) { auto call = std::make_unique(getCallDelegate(), user, type);; if (_currentCall) { @@ -282,7 +290,13 @@ bool Instance::alreadyInCall() { return (_currentCall && _currentCall->state() != Call::State::Busy); } -Instance::~Instance() = default; +Instance::~Instance() { + for (auto panel : _pendingPanels) { + if (panel) { + delete panel; + } + } +} Instance &Current() { return AuthSession::Current().calls(); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 05d8c2eb4c..914dbdb4cd 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -67,6 +67,7 @@ private: void playSound(Sound sound) override; void createCall(gsl::not_null user, Call::Type type); void destroyCall(gsl::not_null call); + void destroyCurrentPanel(); void refreshDhConfig(); void refreshServerConfig(); @@ -83,6 +84,7 @@ private: std::unique_ptr _currentCallPanel; base::Observable _currentCallChanged; base::Observable _newServiceMessage; + std::vector> _pendingPanels; std::unique_ptr _callConnectingTrack; std::unique_ptr _callEndedTrack; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 7990e264cf..c8619d30fa 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "observer_peer.h" #include "platform/platform_specific.h" +#include "base/task_queue.h" namespace Calls { namespace { @@ -145,7 +146,9 @@ void Panel::hideDeactivated() { void Panel::initControls() { _mute->setClickedCallback([this] { - _call->setMute(!_call->isMute()); + if (_call) { + _call->setMute(!_call->isMute()); + } }); subscribe(_call->muteChanged(), [this](bool mute) { _mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr); @@ -167,6 +170,8 @@ void Panel::initControls() { } void Panel::reinitControls() { + Expects(_call != nullptr); + unsubscribe(_stateChangedSubscription); _stateChangedSubscription = subscribe(_call->stateChanged(), [this](State state) { stateChanged(state); }); stateChanged(_call->state()); @@ -180,7 +185,7 @@ void Panel::refreshCallbacks() { if (button) { button->setClickedCallback([this, callback] { if (_call) { - callback(_call.get()); + callback(_call); } }); }; @@ -209,10 +214,47 @@ void Panel::initLayout() { refreshUserPhoto(); }); createDefaultCacheImage(); + toggleOpacityAnimation(true); Platform::InitOnTopPanel(this); } +void Panel::toggleOpacityAnimation(bool visible) { + if (_useTransparency) { + if (_animationCache.isNull()) { + _animationCache = myGrab(this); + hideChildren(); + } + _opacityAnimation.start([this] { update(); }, visible ? 0. : 1., visible ? 1. : 0., st::callPanelDuration, visible ? anim::easeOutCirc : anim::easeInCirc); + } +} + +void Panel::finishAnimation() { + _animationCache = QPixmap(); + if (_call) { + showChildren(); + } else { + destroyDelayed(); + } +} + +void Panel::destroyDelayed() { + hide(); + base::TaskQueue::Main().Put([weak = QPointer(this)] { + if (weak) { + delete weak.data(); + } + }); +} + +void Panel::hideAndDestroy() { + toggleOpacityAnimation(false); + _call = nullptr; + if (_animationCache.isNull()) { + destroyDelayed(); + } +} + void Panel::processUserPhoto() { if (!_user->userpicLoaded()) { _user->loadUserpic(true); @@ -367,6 +409,23 @@ void Panel::updateStatusGeometry() { void Panel::paintEvent(QPaintEvent *e) { Painter p(this); + if (!_animationCache.isNull()) { + auto opacity = _opacityAnimation.current(getms(), _call ? 1. : 0.); + if (!_opacityAnimation.animating()) { + finishAnimation(); + if (!_call) return; + } else { + p.setOpacity(opacity); + + PainterHighQualityEnabler hq(p); + auto marginRatio = (1. - opacity) / 5; + auto marginWidth = qRound(width() * marginRatio); + auto marginHeight = qRound(height() * marginRatio); + p.drawPixmap(rect().marginsRemoved(QMargins(marginWidth, marginHeight, marginWidth, marginHeight)), _animationCache, QRect(QPoint(0, 0), _animationCache.size())); + return; + } + } + if (_useTransparency) { Platform::StartTranslucentPaint(p, e); p.drawPixmapLeft(0, 0, width(), _cache); @@ -472,15 +531,16 @@ void Panel::stateChanged(State state) { syncButton(_hangup, (state != State::Busy), st::callHangup); syncButton(_redial, (state == State::Busy), st::callAnswer); syncButton(_cancel, (state == State::Busy), st::callCancel); + + if (_fingerprint.empty() && _call->isKeyShaForFingerprintReady()) { + fillFingerprint(); + } } if (buttonsUpdated) { refreshCallbacks(); updateControlsGeometry(); } - if (_fingerprint.empty() && _call && _call->isKeyShaForFingerprintReady()) { - fillFingerprint(); - } if ((state == State::Starting) || (state == State::WaitingIncoming)) { Platform::ReInitOnTopPanel(this); } else { @@ -494,7 +554,8 @@ void Panel::stateChanged(State state) { } void Panel::fillFingerprint() { - _fingerprint = ComputeEmojiFingerprint(_call.get()); + Expects(_call != nullptr); + _fingerprint = ComputeEmojiFingerprint(_call); auto realSize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); auto size = realSize / cIntRetinaFactor(); diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 25b94a3d5b..820119b517 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -37,8 +37,8 @@ public: Panel(gsl::not_null call); void showAndActivate(); - void replaceCall(gsl::not_null call); + void hideAndDestroy(); protected: void paintEvent(QPaintEvent *e) override; @@ -80,8 +80,11 @@ private: void updateStatusText(State state); void startDurationUpdateTimer(TimeMs currentDuration); void fillFingerprint(); + void toggleOpacityAnimation(bool visible); + void finishAnimation(); + void destroyDelayed(); - base::weak_unique_ptr _call; + Call *_call = nullptr; gsl::not_null _user; bool _useTransparency = true; @@ -112,8 +115,9 @@ private: PhotoId _userPhotoId = 0; bool _userPhotoFull = false; + Animation _opacityAnimation; + QPixmap _animationCache; QPixmap _bottomCache; - QPixmap _cache; };