Extract Calls::Userpic and Calls::VideoBubble.

This commit is contained in:
John Preston 2020-08-06 20:55:58 +04:00
parent 95de762529
commit d4b8fa70a7
12 changed files with 703 additions and 277 deletions

View File

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

View File

@ -34,6 +34,7 @@ callShadow: Shadow {
fallback: windowShadowFgFallback;
}
callPhotoSize: 180px;
callPhotoSmallSize: 100px;
callOutgoingPreviewSize: size(340px, 180px);
callOutgoingDefaultSize: size(160px, 110px);

View File

@ -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*> call,
const style::CallSignalBars &st,
Fn<void()> 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*> 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*> 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<Userpic>(this, _user, std::move(remoteMuted));
_outgoingVideoBubble = std::make_unique<VideoBubble>(
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);
}
}

View File

@ -34,27 +34,9 @@ struct CallSignalBars;
namespace Calls {
class SignalBars final : public Ui::RpWidget {
public:
SignalBars(
QWidget *parent,
not_null<Call*> call,
const style::CallSignalBars &st,
Fn<void()> 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<void()> _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<UserData*> _user;
std::shared_ptr<Data::CloudImageView> _userpic;
std::shared_ptr<Data::PhotoMedia> _photo;
bool _useTransparency = true;
bool _incomingShown = false;
@ -142,7 +121,9 @@ private:
object_ptr<Ui::IconButton> _mute;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<SignalBars> _signalBars;
object_ptr<SignalBars> _signalBars = { nullptr };
std::unique_ptr<Userpic> _userpic;
std::unique_ptr<VideoBubble> _outgoingVideoBubble;
std::vector<EmojiPtr> _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;

View File

@ -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*> call,
const style::CallSignalBars &st,
Fn<void()> 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

View File

@ -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*> call,
const style::CallSignalBars &st,
Fn<void()> displayedChangedCallback = nullptr);
bool isDisplayed() const;
protected:
void paintEvent(QPaintEvent *e) override;
private:
void changed(int count);
const style::CallSignalBars &_st;
int _count = 0;
Fn<void()> _displayedChangedCallback;
};
} // namespace Calls

View File

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

View File

@ -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<QWidget*> parent,
not_null<PeerData*> peer,
rpl::producer<bool> 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<bool> 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

View File

@ -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<QWidget*> parent,
not_null<PeerData*> peer,
rpl::producer<bool> 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<bool> 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<PeerData*> _peer;
std::shared_ptr<Data::CloudImageView> _userpic;
std::shared_ptr<Data::PhotoMedia> _photo;
Ui::Animations::Simple _mutedAnimation;
QPixmap _userPhoto;
PhotoId _userPhotoId = 0;
bool _userPhotoFull = false;
bool _muted = false;
};
} // namespace Calls

View File

@ -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<QWidget*> parent,
not_null<webrtc::VideoTrack*> 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

View File

@ -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<QWidget*> parent,
not_null<webrtc::VideoTrack*> 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<webrtc::VideoTrack*> _track;
webrtc::VideoState _state = webrtc::VideoState();
QImage _pausedFrame;
QSize _min, _max, _size, _lastFrameSize;
QRect _boundingRect;
DragMode _dragMode = DragMode::None;
bool _dragging = false;
};
} // namespace Calls

@ -1 +1 @@
Subproject commit 4d13b96b4c4e4be2ada7e460203eea9fecde458d
Subproject commit a8e19691c5d653310f7ac5f75d69e45b34771fa4