Add call panel show / hide animation.

This commit is contained in:
John Preston 2017-05-04 16:32:56 +03:00
parent 299dc3fc96
commit 2214e980ef
6 changed files with 96 additions and 21 deletions

View File

@ -188,3 +188,4 @@ callRatingCommentTop: 2px;
callDebugLabel: FlatLabel(defaultFlatLabel) {
margin: margins(24px, 0px, 24px, 0px);
}
callPanelDuration: 200;

View File

@ -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:

View File

@ -98,7 +98,7 @@ void Instance::playSound(Sound sound) {
void Instance::destroyCall(gsl::not_null<Call*> 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*> 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<UserData*> user, Call::Type type) {
auto call = std::make_unique<Call>(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();

View File

@ -67,6 +67,7 @@ private:
void playSound(Sound sound) override;
void createCall(gsl::not_null<UserData*> user, Call::Type type);
void destroyCall(gsl::not_null<Call*> call);
void destroyCurrentPanel();
void refreshDhConfig();
void refreshServerConfig();
@ -83,6 +84,7 @@ private:
std::unique_ptr<Panel> _currentCallPanel;
base::Observable<Call*> _currentCallChanged;
base::Observable<FullMsgId> _newServiceMessage;
std::vector<QPointer<Panel>> _pendingPanels;
std::unique_ptr<Media::Audio::Track> _callConnectingTrack;
std::unique_ptr<Media::Audio::Track> _callEndedTrack;

View File

@ -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<Panel>(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();

View File

@ -37,8 +37,8 @@ public:
Panel(gsl::not_null<Call*> call);
void showAndActivate();
void replaceCall(gsl::not_null<Call*> 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 *_call = nullptr;
gsl::not_null<UserData*> _user;
bool _useTransparency = true;
@ -112,8 +115,9 @@ private:
PhotoId _userPhotoId = 0;
bool _userPhotoFull = false;
Animation _opacityAnimation;
QPixmap _animationCache;
QPixmap _bottomCache;
QPixmap _cache;
};