From d4b8fa70a794c32d7ea8f7a4cbf7ae11618f43c8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Aug 2020 20:55:58 +0400 Subject: [PATCH] Extract Calls::Userpic and Calls::VideoBubble. --- Telegram/CMakeLists.txt | 6 + Telegram/SourceFiles/calls/calls.style | 1 + Telegram/SourceFiles/calls/calls_panel.cpp | 341 +++++------------- Telegram/SourceFiles/calls/calls_panel.h | 42 +-- .../SourceFiles/calls/calls_signal_bars.cpp | 77 ++++ .../SourceFiles/calls/calls_signal_bars.h | 42 +++ Telegram/SourceFiles/calls/calls_top_bar.cpp | 2 +- Telegram/SourceFiles/calls/calls_userpic.cpp | 192 ++++++++++ Telegram/SourceFiles/calls/calls_userpic.h | 63 ++++ .../SourceFiles/calls/calls_video_bubble.cpp | 154 ++++++++ .../SourceFiles/calls/calls_video_bubble.h | 58 +++ Telegram/lib_webrtc | 2 +- 12 files changed, 703 insertions(+), 277 deletions(-) create mode 100644 Telegram/SourceFiles/calls/calls_signal_bars.cpp create mode 100644 Telegram/SourceFiles/calls/calls_signal_bars.h create mode 100644 Telegram/SourceFiles/calls/calls_userpic.cpp create mode 100644 Telegram/SourceFiles/calls/calls_userpic.h create mode 100644 Telegram/SourceFiles/calls/calls_video_bubble.cpp create mode 100644 Telegram/SourceFiles/calls/calls_video_bubble.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 14b902a462..b0e6dc4f02 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -324,8 +324,14 @@ PRIVATE calls/calls_instance.h calls/calls_panel.cpp calls/calls_panel.h + calls/calls_signal_bars.cpp + calls/calls_signal_bars.h calls/calls_top_bar.cpp calls/calls_top_bar.h + calls/calls_userpic.cpp + calls/calls_userpic.h + calls/calls_video_bubble.cpp + calls/calls_video_bubble.h chat_helpers/bot_keyboard.cpp chat_helpers/bot_keyboard.h chat_helpers/emoji_keywords.cpp diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 8a2cc8e9aa..16a54fbe35 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -34,6 +34,7 @@ callShadow: Shadow { fallback: windowShadowFgFallback; } callPhotoSize: 180px; +callPhotoSmallSize: 100px; callOutgoingPreviewSize: size(340px, 180px); callOutgoingDefaultSize: size(160px, 110px); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 1799761d26..11a977e540 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -15,6 +15,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_cloud_file.h" #include "data/data_changes.h" #include "calls/calls_emoji_fingerprint.h" +#include "calls/calls_signal_bars.h" +#include "calls/calls_userpic.h" +#include "calls/calls_video_bubble.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" @@ -79,67 +82,6 @@ private: }; -SignalBars::SignalBars( - QWidget *parent, - not_null call, - const style::CallSignalBars &st, - Fn displayedChangedCallback) -: RpWidget(parent) -, _st(st) -, _displayedChangedCallback(std::move(displayedChangedCallback)) { - resize( - _st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1), - _st.width * Call::kSignalBarCount); - call->signalBarCountValue( - ) | rpl::start_with_next([=](int count) { - changed(count); - }, lifetime()); -} - -bool SignalBars::isDisplayed() const { - return (_count >= 0); -} - -void SignalBars::paintEvent(QPaintEvent *e) { - if (!isDisplayed()) { - return; - } - - Painter p(this); - - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - p.setBrush(_st.color); - for (auto i = 0; i < Call::kSignalBarCount; ++i) { - p.setOpacity((i < _count) ? 1. : _st.inactiveOpacity); - const auto barHeight = (i + 1) * _st.width; - const auto barLeft = i * (_st.width + _st.skip); - const auto barTop = height() - barHeight; - p.drawRoundedRect( - barLeft, - barTop, - _st.width, - barHeight, - _st.radius, - _st.radius); - } - p.setOpacity(1.); -} - -void SignalBars::changed(int count) { - if (_count == Call::kSignalBarFinished) { - return; - } - if (_count != count) { - const auto wasDisplayed = isDisplayed(); - _count = count; - if (isDisplayed() != wasDisplayed && _displayedChangedCallback) { - _displayedChangedCallback(); - } - update(); - } -} - Panel::Button::Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo) : Ui::RippleButton(parent, stFrom.button.ripple) , _stFrom(&stFrom) , _stTo(stTo) { @@ -312,8 +254,7 @@ Panel::Panel(not_null call) , _camera(this, st::callCameraToggle) , _mute(this, st::callMuteToggle) , _name(this, st::callName) -, _status(this, st::callStatus) -, _signalBars(this, call, st::callPanelSignalBars) { +, _status(this, st::callStatus) { _decline->setDuration(st::callPanelDuration); _cancel->setDuration(st::callPanelDuration); @@ -341,9 +282,7 @@ void Panel::replaceCall(not_null call) { bool Panel::eventHook(QEvent *e) { if (e->type() == QEvent::WindowDeactivate) { - if (_call && _call->state() == State::Established) { - hideDeactivated(); - } + checkForInactiveHide(); } return RpWidget::eventHook(e); } @@ -422,11 +361,29 @@ void Panel::reinitWithCall(Call *call) { _callLifetime.destroy(); _call = call; if (!_call) { + _outgoingVideoBubble = nullptr; return; } _user = _call->user(); + _signalBars.create( + this, + _call, + st::callPanelSignalBars, + [=] { rtlupdate(signalBarsRect()); }); + + auto remoteMuted = _call->remoteAudioStateValue( + ) | rpl::map([=](Call::RemoteAudioState state) { + return (state == Call::RemoteAudioState::Muted); + }); + _userpic = std::make_unique(this, _user, std::move(remoteMuted)); + _outgoingVideoBubble = std::make_unique( + this, + _call->videoOutgoing()); + _outgoingVideoBubble->setSizeConstraints( + st::callOutgoingPreviewSize); + _call->mutedValue( ) | rpl::start_with_next([=](bool mute) { _mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr); @@ -444,53 +401,42 @@ void Panel::reinitWithCall(Call *call) { stateChanged(state); }, _callLifetime); - rpl::merge( - _call->videoIncoming()->renderNextFrame(), - _call->videoOutgoing()->renderNextFrame() + _call->videoIncoming()->renderNextFrame( ) | rpl::start_with_next([=] { setIncomingShown(!_call->videoIncoming()->frame({}).isNull()); update(); }, _callLifetime); - _signalBars.create( - this, - _call, - st::callPanelSignalBars, - [=] { rtlupdate(signalBarsRect()); }); + rpl::merge( + _call->videoIncoming()->stateChanges(), + _call->videoOutgoing()->stateChanges() + ) | rpl::start_with_next([=] { + checkForInactiveShow(); + }, _callLifetime); _name->setText(_user->name); updateStatusText(_call->state()); } void Panel::initLayout() { - setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::Dialog); + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::NoDropShadowWindowHint | Qt::Dialog); setAttribute(Qt::WA_MacAlwaysShowToolWindow); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_TranslucentBackground, true); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); initGeometry(); using UpdateFlag = Data::PeerUpdate::Flag; _user->session().changes().peerUpdates( - UpdateFlag::Name | UpdateFlag::Photo + UpdateFlag::Name ) | rpl::filter([=](const Data::PeerUpdate &update) { // _user may change for the same Panel. return (_call != nullptr) && (update.peer == _user); }) | rpl::start_with_next([=](const Data::PeerUpdate &update) { - if (update.flags & UpdateFlag::Name) { - _name->setText(_call->user()->name); - updateControlsGeometry(); - } - if (update.flags & UpdateFlag::Photo) { - processUserPhoto(); - } + _name->setText(_call->user()->name); + updateControlsGeometry(); }, lifetime()); - processUserPhoto(); - _user->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - refreshUserPhoto(); - }, lifetime()); createDefaultCacheImage(); Ui::Platform::InitOnTopPanel(this); @@ -543,6 +489,7 @@ void Panel::showControls() { _cancel->setVisible(_cancel->toggled()); _name->setVisible(!_incomingShown); _status->setVisible(!_incomingShown); + _userpic->setVisible(!_incomingShown); } void Panel::destroyDelayed() { @@ -560,90 +507,6 @@ void Panel::hideAndDestroy() { } } -void Panel::processUserPhoto() { - _userpic = _user->createUserpicView(); - _user->loadUserpic(); - const auto photo = _user->userpicPhotoId() - ? _user->owner().photo(_user->userpicPhotoId()).get() - : nullptr; - if (isGoodUserPhoto(photo)) { - _photo = photo->createMediaView(); - _photo->wanted(Data::PhotoSize::Large, _user->userpicPhotoOrigin()); - } else { - _photo = nullptr; - if (_user->userpicPhotoUnknown() || (photo && !photo->date)) { - _user->session().api().requestFullPeer(_user); - } - } - refreshUserPhoto(); -} - -void Panel::refreshUserPhoto() { - const auto isNewBigPhoto = [&] { - return _photo - && _photo->loaded() - && (_photo->owner()->id != _userPhotoId || !_userPhotoFull); - }(); - if (isNewBigPhoto) { - _userPhotoId = _photo->owner()->id; - _userPhotoFull = true; - createUserpicCache(_photo->image(Data::PhotoSize::Large)); - } else if (_userPhoto.isNull()) { - createUserpicCache(_userpic ? _userpic->image() : nullptr); - } -} - -void Panel::createUserpicCache(Image *image) { - auto size = st::callPhotoSize * cIntRetinaFactor(); - auto options = Images::Option::Smooth | Images::Option::Circled; - // _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None; - if (image) { - auto width = image->width(); - auto height = image->height(); - if (width > height) { - width = qMax((width * size) / height, 1); - height = size; - } else { - height = qMax((height * size) / width, 1); - width = size; - } - _userPhoto = image->pixNoCache( - width, - height, - options, - st::callPhotoSize, - st::callPhotoSize); - _userPhoto.setDevicePixelRatio(cRetinaFactor()); - } else { - auto filled = QImage(QSize(st::callPhotoSize, st::callPhotoSize) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - filled.setDevicePixelRatio(cRetinaFactor()); - { - Painter p(&filled); - Ui::EmptyUserpic( - Data::PeerUserpicColor(_user->id), - _user->name - ).paint(p, 0, 0, st::callPhotoSize, st::callPhotoSize); - } - //Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight); - _userPhoto = App::pixmapFromImageInPlace(std::move(filled)); - } - refreshCacheImageUserPhoto(); - - update(); -} - -bool Panel::isGoodUserPhoto(PhotoData *photo) { - if (!photo || photo->isNull()) { - return false; - } - const auto badAspect = [](int a, int b) { - return a > 10 * b; - }; - const auto width = photo->width(); - const auto height = photo->height(); - return !badAspect(width, height) && !badAspect(height, width); -} - void Panel::initGeometry() { const auto center = Core::App().getPointForCallPanelCenter(); _useTransparency = Ui::Platform::TranslucentWindowsSupported(center); @@ -703,26 +566,23 @@ void Panel::createDefaultCacheImage() { _cache = App::pixmapFromImageInPlace(std::move(cache)); } -void Panel::refreshCacheImageUserPhoto() { - auto cache = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - cache.setDevicePixelRatio(cRetinaFactor()); - cache.fill(Qt::transparent); - { - Painter p(&cache); - p.drawPixmapLeft(0, 0, width(), _bottomCache); - p.drawPixmapLeft((width() - st::callPhotoSize) / 2, st::callPhotoSize, width(), _userPhoto); - } - _cache = App::pixmapFromImageInPlace(std::move(cache)); -} - void Panel::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void Panel::updateControlsGeometry() { + const auto size = st::callPhotoSize; + _userpic->setGeometry((width() - size) / 2, st::callPhotoSize, size); _name->moveToLeft((width() - _name->width()) / 2, _contentTop + st::callNameTop); updateStatusGeometry(); + _outgoingVideoBubble->setBoundingRect({ + (width() - st::callOutgoingPreviewSize.width()) / 2, + _contentTop + st::callStatusTop + _status->height(), + st::callOutgoingPreviewSize.width(), + st::callOutgoingPreviewSize.height() + }); + auto controlsTop = _padding.top() + st::callControlsTop; auto bothWidth = _answerHangupRedial->width() + st::callControlsSkip + st::callCancel.button.width; _decline->moveToLeft((width() - bothWidth) / 2, controlsTop); @@ -777,7 +637,6 @@ void Panel::paintEvent(QPaintEvent *e) { if (_useTransparency) { p.drawPixmapLeft(0, 0, width(), _cache); } else { - p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto); auto callBgOpaque = st::callBg->c; callBgOpaque.setAlpha(255); p.fillRect(rect(), QBrush(callBgOpaque)); @@ -800,28 +659,6 @@ void Panel::paintEvent(QPaintEvent *e) { } _call->videoIncoming()->markFrameShown(); - const auto outgoingFrame = _call - ? _call->videoOutgoing()->frame(webrtc::FrameRequest()) - : QImage(); - if (!outgoingFrame.isNull()) { - const auto size = QSize(width() / 3, height() / 3); - const auto to = QRect( - width() - 2 * _padding.right() - size.width(), - 2 * _padding.bottom(), - size.width(), - size.height()); - p.save(); - p.setClipRect(to); - const auto big = outgoingFrame.size().scaled(to.size(), Qt::KeepAspectRatioByExpanding); - const auto pos = QPoint( - to.left() + (to.width() - big.width()) / 2, - to.top() + (to.height() - big.height()) / 2); - auto hq = PainterHighQualityEnabler(p); - p.drawImage(QRect(pos, big), outgoingFrame); - p.restore(); - } - _call->videoOutgoing()->markFrameShown(); - if (_signalBars->isDisplayed()) { paintSignalBarsBg(p); } @@ -920,51 +757,69 @@ bool Panel::tooltipWindowActive() const { } void Panel::stateChanged(State state) { + Expects(_call != nullptr); + updateStatusText(state); - if (_call) { - if ((state != State::HangingUp) - && (state != State::Ended) - && (state != State::EndedByOtherDevice) - && (state != State::FailedHangingUp) - && (state != State::Failed)) { - auto toggleButton = [this](auto &&button, bool visible) { - button->toggle( - visible, - isHidden() - ? anim::type::instant - : anim::type::normal); - }; - auto incomingWaiting = _call->isIncomingWaiting(); - if (incomingWaiting) { - _updateOuterRippleTimer.callEach(Call::kSoundSampleMs); - } - toggleButton(_decline, incomingWaiting); - toggleButton(_cancel, (state == State::Busy)); - auto hangupShown = !_decline->toggled() - && !_cancel->toggled(); - if (_hangupShown != hangupShown) { - _hangupShown = hangupShown; - _hangupShownProgress.start([this] { updateHangupGeometry(); }, _hangupShown ? 0. : 1., _hangupShown ? 1. : 0., st::callPanelDuration, anim::sineInOut); - } - if (_fingerprint.empty() && _call->isKeyShaForFingerprintReady()) { - fillFingerprint(); - } + if ((state != State::HangingUp) + && (state != State::Ended) + && (state != State::EndedByOtherDevice) + && (state != State::FailedHangingUp) + && (state != State::Failed)) { + auto toggleButton = [this](auto &&button, bool visible) { + button->toggle( + visible, + isHidden() + ? anim::type::instant + : anim::type::normal); + }; + auto incomingWaiting = _call->isIncomingWaiting(); + if (incomingWaiting) { + _updateOuterRippleTimer.callEach(Call::kSoundSampleMs); + } + toggleButton(_decline, incomingWaiting); + toggleButton(_cancel, (state == State::Busy)); + auto hangupShown = !_decline->toggled() + && !_cancel->toggled(); + if (_hangupShown != hangupShown) { + _hangupShown = hangupShown; + _hangupShownProgress.start([this] { updateHangupGeometry(); }, _hangupShown ? 0. : 1., _hangupShown ? 1. : 0., st::callPanelDuration, anim::sineInOut); + } + if (_fingerprint.empty() && _call->isKeyShaForFingerprintReady()) { + fillFingerprint(); } } - if (windowHandle()) { - // First stateChanged() is called before the first Platform::InitOnTopPanel(this). + // First stateChanged() is called before + // the first Platform::InitOnTopPanel(this). if ((state == State::Starting) || (state == State::WaitingIncoming)) { Ui::Platform::ReInitOnTopPanel(this); } else { Ui::Platform::DeInitOnTopPanel(this); } + checkForInactiveHide(); } - if (state == State::Established) { - if (!isActiveWindow()) { - hideDeactivated(); - } +} + +bool Panel::hasActiveVideo() const { + const auto inactive = webrtc::VideoState::Inactive; + return (_call->videoIncoming()->state() != inactive) + || (_call->videoOutgoing()->state() != inactive); +} + +void Panel::checkForInactiveHide() { + if (!_call + || (_call->state() != State::Established) + || isActiveWindow() + || hasActiveVideo()) { + return; + } + hideDeactivated(); +} + +void Panel::checkForInactiveShow() { + if (!_visible && hasActiveVideo()) { + toggleOpacityAnimation(true); } } diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 590da2a737..7d168d9af0 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -34,27 +34,9 @@ struct CallSignalBars; namespace Calls { -class SignalBars final : public Ui::RpWidget { -public: - SignalBars( - QWidget *parent, - not_null call, - const style::CallSignalBars &st, - Fn displayedChangedCallback = nullptr); - - bool isDisplayed() const; - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - void changed(int count); - - const style::CallSignalBars &_st; - int _count = Call::kSignalBarStarting; - Fn _displayedChangedCallback; - -}; +class Userpic; +class SignalBars; +class VideoBubble; class Panel final : public Ui::RpWidget, private Ui::AbstractTooltipShower { @@ -93,12 +75,7 @@ private: void hideDeactivated(); void createBottomImage(); void createDefaultCacheImage(); - void refreshCacheImageUserPhoto(); - void processUserPhoto(); - void refreshUserPhoto(); - bool isGoodUserPhoto(PhotoData *photo); - void createUserpicCache(Image *image); QRect signalBarsRect() const; void paintSignalBarsBg(Painter &p); @@ -115,10 +92,12 @@ private: void destroyDelayed(); void setIncomingShown(bool shown); + [[nodiscard]] bool hasActiveVideo() const; + void checkForInactiveHide(); + void checkForInactiveShow(); + Call *_call = nullptr; not_null _user; - std::shared_ptr _userpic; - std::shared_ptr _photo; bool _useTransparency = true; bool _incomingShown = false; @@ -142,7 +121,9 @@ private: object_ptr _mute; object_ptr _name; object_ptr _status; - object_ptr _signalBars; + object_ptr _signalBars = { nullptr }; + std::unique_ptr _userpic; + std::unique_ptr _outgoingVideoBubble; std::vector _fingerprint; QRect _fingerprintArea; @@ -150,9 +131,6 @@ private: base::Timer _updateOuterRippleTimer; bool _visible = false; - QPixmap _userPhoto; - PhotoId _userPhotoId = 0; - bool _userPhotoFull = false; Ui::Animations::Simple _opacityAnimation; QPixmap _animationCache; diff --git a/Telegram/SourceFiles/calls/calls_signal_bars.cpp b/Telegram/SourceFiles/calls/calls_signal_bars.cpp new file mode 100644 index 0000000000..b52f26404c --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_signal_bars.cpp @@ -0,0 +1,77 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "calls/calls_signal_bars.h" + +#include "calls/calls_call.h" +#include "styles/style_calls.h" + +namespace Calls { + +SignalBars::SignalBars( + QWidget *parent, + not_null call, + const style::CallSignalBars &st, + Fn displayedChangedCallback) +: RpWidget(parent) +, _st(st) +, _count(Call::kSignalBarStarting) +, _displayedChangedCallback(std::move(displayedChangedCallback)) { + resize( + _st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1), + _st.width * Call::kSignalBarCount); + call->signalBarCountValue( + ) | rpl::start_with_next([=](int count) { + changed(count); + }, lifetime()); +} + +bool SignalBars::isDisplayed() const { + return (_count >= 0); +} + +void SignalBars::paintEvent(QPaintEvent *e) { + if (!isDisplayed()) { + return; + } + + Painter p(this); + + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(_st.color); + for (auto i = 0; i < Call::kSignalBarCount; ++i) { + p.setOpacity((i < _count) ? 1. : _st.inactiveOpacity); + const auto barHeight = (i + 1) * _st.width; + const auto barLeft = i * (_st.width + _st.skip); + const auto barTop = height() - barHeight; + p.drawRoundedRect( + barLeft, + barTop, + _st.width, + barHeight, + _st.radius, + _st.radius); + } + p.setOpacity(1.); +} + +void SignalBars::changed(int count) { + if (_count == Call::kSignalBarFinished) { + return; + } + if (_count != count) { + const auto wasDisplayed = isDisplayed(); + _count = count; + if (isDisplayed() != wasDisplayed && _displayedChangedCallback) { + _displayedChangedCallback(); + } + update(); + } +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_signal_bars.h b/Telegram/SourceFiles/calls/calls_signal_bars.h new file mode 100644 index 0000000000..0be132fb0f --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_signal_bars.h @@ -0,0 +1,42 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace style { +struct CallSignalBars; +} // namespace style + +namespace Calls { + +class Call; + +class SignalBars final : public Ui::RpWidget { +public: + SignalBars( + QWidget *parent, + not_null call, + const style::CallSignalBars &st, + Fn displayedChangedCallback = nullptr); + + bool isDisplayed() const; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void changed(int count); + + const style::CallSignalBars &_st; + int _count = 0; + Fn _displayedChangedCallback; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index 1122b2cfaa..cac80a4688 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "calls/calls_call.h" #include "calls/calls_instance.h" -#include "calls/calls_panel.h" +#include "calls/calls_signal_bars.h" #include "data/data_user.h" #include "data/data_changes.h" #include "main/main_session.h" diff --git a/Telegram/SourceFiles/calls/calls_userpic.cpp b/Telegram/SourceFiles/calls/calls_userpic.cpp new file mode 100644 index 0000000000..a36c160bcb --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_userpic.cpp @@ -0,0 +1,192 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "calls/calls_userpic.h" + +#include "data/data_peer.h" +#include "main/main_session.h" +#include "data/data_changes.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_cloud_file.h" +#include "data/data_photo_media.h" +#include "data/data_file_origin.h" +#include "ui/empty_userpic.h" +#include "apiwrap.h" // requestFullPeer. +#include "styles/style_calls.h" + +namespace Calls { +namespace { + +} // namespace + +Userpic::Userpic( + not_null parent, + not_null peer, + rpl::producer muted) +: _content(parent) +, _peer(peer) { + setGeometry(0, 0, 0); + setup(std::move(muted)); +} + +Userpic::~Userpic() = default; + +void Userpic::setVisible(bool visible) { + _content.setVisible(visible); +} + +void Userpic::setGeometry(int x, int y, int size) { + if (this->size() != size) { + _userPhoto = QPixmap(); + _userPhotoFull = false; + refreshPhoto(); + } + _content.setGeometry(x, y, size, size); + _content.update(); +} + +void Userpic::setup(rpl::producer muted) { + _content.show(); + _content.setAttribute(Qt::WA_TransparentForMouseEvents); + + _content.paintRequest( + ) | rpl::start_with_next([=] { + paint(); + }, lifetime()); + + std::move( + muted + ) | rpl::start_with_next([=](bool muted) { + setMuted(muted); + }, lifetime()); + + _peer->session().changes().peerFlagsValue( + _peer, + Data::PeerUpdate::Flag::Photo + ) | rpl::start_with_next([=] { + processPhoto(); + }, lifetime()); + + _peer->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + refreshPhoto(); + }, lifetime()); + + _mutedAnimation.stop(); +} + +void Userpic::paint() { + Painter p(&_content); + + p.drawPixmap(0, 0, _userPhoto); +} + +void Userpic::setMuted(bool muted) { + if (_muted == muted) { + return; + } + _muted = muted; + _mutedAnimation.start( + [=] { _content.update(); }, + _muted ? 0. : 1., + _muted ? 1. : 0., + st::fadeWrapDuration); +} + +int Userpic::size() const { + return _content.width(); +} + +void Userpic::processPhoto() { + _userpic = _peer->createUserpicView(); + _peer->loadUserpic(); + const auto photo = _peer->userpicPhotoId() + ? _peer->owner().photo(_peer->userpicPhotoId()).get() + : nullptr; + if (isGoodPhoto(photo)) { + _photo = photo->createMediaView(); + _photo->wanted(Data::PhotoSize::Thumbnail, _peer->userpicPhotoOrigin()); + } else { + _photo = nullptr; + if (_peer->userpicPhotoUnknown() || (photo && !photo->date)) { + _peer->session().api().requestFullPeer(_peer); + } + } + refreshPhoto(); +} + +void Userpic::refreshPhoto() { + if (!size()) { + return; + } + const auto isNewBigPhoto = [&] { + return _photo + && (_photo->image(Data::PhotoSize::Thumbnail) != nullptr) + && (_photo->owner()->id != _userPhotoId || !_userPhotoFull); + }(); + if (isNewBigPhoto) { + _userPhotoId = _photo->owner()->id; + _userPhotoFull = true; + createCache(_photo->image(Data::PhotoSize::Thumbnail)); + } else if (_userPhoto.isNull()) { + createCache(_userpic ? _userpic->image() : nullptr); + } +} + +void Userpic::createCache(Image *image) { + auto size = this->size() * cIntRetinaFactor(); + auto options = Images::Option::Smooth | Images::Option::Circled; + // _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None; + if (image) { + auto width = image->width(); + auto height = image->height(); + if (width > height) { + width = qMax((width * size) / height, 1); + height = size; + } else { + height = qMax((height * size) / width, 1); + width = size; + } + _userPhoto = image->pixNoCache( + width, + height, + options, + st::callPhotoSize, + st::callPhotoSize); + _userPhoto.setDevicePixelRatio(cRetinaFactor()); + } else { + auto filled = QImage(QSize(st::callPhotoSize, st::callPhotoSize) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + filled.setDevicePixelRatio(cRetinaFactor()); + filled.fill(Qt::transparent); + { + Painter p(&filled); + Ui::EmptyUserpic( + Data::PeerUserpicColor(_peer->id), + _peer->name + ).paint(p, 0, 0, st::callPhotoSize, st::callPhotoSize); + } + //Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight); + _userPhoto = Images::PixmapFast(std::move(filled)); + } + + _content.update(); +} + +bool Userpic::isGoodPhoto(PhotoData *photo) const { + if (!photo || photo->isNull()) { + return false; + } + const auto badAspect = [](int a, int b) { + return a > 10 * b; + }; + const auto width = photo->width(); + const auto height = photo->height(); + return !badAspect(width, height) && !badAspect(height, width); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_userpic.h b/Telegram/SourceFiles/calls/calls_userpic.h new file mode 100644 index 0000000000..8680e76ec3 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_userpic.h @@ -0,0 +1,63 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" + +class PeerData; +class Image; + +namespace Data { +class CloudImageView; +class PhotoMedia; +} // namespace Data + +namespace Calls { + +class Userpic final { +public: + Userpic( + not_null parent, + not_null peer, + rpl::producer muted); + ~Userpic(); + + void setVisible(bool visible); + void setGeometry(int x, int y, int size); + + [[nodiscard]] rpl::lifetime &lifetime() { + return _content.lifetime(); + } + +private: + void setup(rpl::producer muted); + + void paint(); + void setMuted(bool muted); + [[nodiscard]] int size() const; + + void processPhoto(); + void refreshPhoto(); + [[nodiscard]] bool isGoodPhoto(PhotoData *photo) const; + void createCache(Image *image); + + Ui::RpWidget _content; + + not_null _peer; + std::shared_ptr _userpic; + std::shared_ptr _photo; + Ui::Animations::Simple _mutedAnimation; + QPixmap _userPhoto; + PhotoId _userPhotoId = 0; + bool _userPhotoFull = false; + bool _muted = false; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_video_bubble.cpp b/Telegram/SourceFiles/calls/calls_video_bubble.cpp new file mode 100644 index 0000000000..7f2f7a0cc6 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_video_bubble.cpp @@ -0,0 +1,154 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "calls/calls_video_bubble.h" + +#include "webrtc/webrtc_video_track.h" +#include "ui/image/image_prepare.h" +#include "styles/style_calls.h" + +namespace Calls { + +VideoBubble::VideoBubble( + not_null parent, + not_null track) +: _content(parent) +, _track(track) +, _state(webrtc::VideoState::Inactive) { + setup(); +} + +void VideoBubble::setup() { + _content.show(); + applyDragMode(_dragMode); + + _content.paintRequest( + ) | rpl::start_with_next([=] { + paint(); + }, lifetime()); + + _track->stateValue( + ) | rpl::start_with_next([=](webrtc::VideoState state) { + setState(state); + }, lifetime()); + + _track->renderNextFrame( + ) | rpl::start_with_next([=] { + if (_track->frameSize().isEmpty()) { + _track->markFrameShown(); + } else { + updateVisibility(); + _content.update(); + } + }, lifetime()); +} + +void VideoBubble::setDragMode(DragMode mode) { + if (_dragMode != mode) { + applyDragMode(mode); + } +} + +void VideoBubble::setBoundingRect(QRect rect) { + _boundingRect = rect; + setSizeConstraints(rect.size()); +} + +void VideoBubble::applyDragMode(DragMode mode) { + _dragMode = mode; + if (_dragMode == DragMode::None) { + _dragging = false; + _content.setCursor(style::cur_default); + } + _content.setAttribute( + Qt::WA_TransparentForMouseEvents, + (_dragMode == DragMode::None)); +} + +void VideoBubble::setSizeConstraints(QSize min, QSize max) { + Expects(!min.isEmpty()); + Expects(max.isEmpty() || min.width() <= max.width()); + Expects(max.isEmpty() || min.height() <= max.height()); + + if (max.isEmpty()) { + max = min; + } + applySizeConstraints(min, max); +} + +void VideoBubble::applySizeConstraints(QSize min, QSize max) { + _min = min; + _max = max; +} + +void VideoBubble::paint() { + Painter p(&_content); + + auto hq = PainterHighQualityEnabler(p); + p.drawImage(_content.rect(), _track->frame({})); + _track->markFrameShown(); +} + +void VideoBubble::setState(webrtc::VideoState state) { + if (state == webrtc::VideoState::Paused) { + using namespace Images; + static constexpr auto kRadius = 24; + _pausedFrame = Images::BlurLargeImage(_track->frame({}), kRadius); + if (_pausedFrame.isNull()) { + state = webrtc::VideoState::Inactive; + } + } + _state = state; + updateVisibility(); +} + +void VideoBubble::updateSizeToFrame(QSize frame) { + Expects(!frame.isEmpty()); + + if (_lastFrameSize == frame) { + return; + } + _lastFrameSize = frame; + + auto size = _size; + if (size.isEmpty()) { + size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio); + } else { + const auto area = size.width() * size.height(); + const auto w = int(std::round(std::max( + std::sqrt((frame.width() * area) / (frame.height() * 1.)), + 1.))); + const auto h = area / w; + size = QSize(w, h); + } + size = QSize(std::max(1, size.width()), std::max(1, size.height())); + setInnerSize(size); +} + +void VideoBubble::setInnerSize(QSize size) { + if (_size == size) { + return; + } + _size = size; + _content.setGeometry( + _boundingRect.x() + (_boundingRect.width() - size.width()) / 2, + _boundingRect.y() + (_boundingRect.height() - size.height()) / 2, + size.width(), + size.height()); +} + +void VideoBubble::updateVisibility() { + const auto size = _track->frameSize(); + const auto visible = (_state != webrtc::VideoState::Inactive) + && !size.isEmpty(); + if (visible) { + updateSizeToFrame(size); + } + _content.setVisible(visible); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_video_bubble.h b/Telegram/SourceFiles/calls/calls_video_bubble.h new file mode 100644 index 0000000000..ea9380d48b --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_video_bubble.h @@ -0,0 +1,58 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace webrtc { +class VideoTrack; +enum class VideoState; +} // namespace webrtc + +namespace Calls { + +class VideoBubble final { +public: + VideoBubble( + not_null parent, + not_null track); + + enum class DragMode { + None, + SnapToCorners, + }; + void setDragMode(DragMode mode); + void setBoundingRect(QRect rect); + void setSizeConstraints(QSize min, QSize max = QSize()); + + [[nodiscard]] rpl::lifetime &lifetime() { + return _content.lifetime(); + } + +private: + void setup(); + void paint(); + void setState(webrtc::VideoState state); + void applyDragMode(DragMode mode); + void applySizeConstraints(QSize min, QSize max); + void updateSizeToFrame(QSize frame); + void updateVisibility(); + void setInnerSize(QSize size); + + Ui::RpWidget _content; + const not_null _track; + webrtc::VideoState _state = webrtc::VideoState(); + QImage _pausedFrame; + QSize _min, _max, _size, _lastFrameSize; + QRect _boundingRect; + DragMode _dragMode = DragMode::None; + bool _dragging = false; + +}; + +} // namespace Calls diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 4d13b96b4c..a8e19691c5 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 4d13b96b4c4e4be2ada7e460203eea9fecde458d +Subproject commit a8e19691c5d653310f7ac5f75d69e45b34771fa4