Extract Calls::Userpic and Calls::VideoBubble.
This commit is contained in:
parent
95de762529
commit
d4b8fa70a7
|
@ -324,8 +324,14 @@ PRIVATE
|
||||||
calls/calls_instance.h
|
calls/calls_instance.h
|
||||||
calls/calls_panel.cpp
|
calls/calls_panel.cpp
|
||||||
calls/calls_panel.h
|
calls/calls_panel.h
|
||||||
|
calls/calls_signal_bars.cpp
|
||||||
|
calls/calls_signal_bars.h
|
||||||
calls/calls_top_bar.cpp
|
calls/calls_top_bar.cpp
|
||||||
calls/calls_top_bar.h
|
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.cpp
|
||||||
chat_helpers/bot_keyboard.h
|
chat_helpers/bot_keyboard.h
|
||||||
chat_helpers/emoji_keywords.cpp
|
chat_helpers/emoji_keywords.cpp
|
||||||
|
|
|
@ -34,6 +34,7 @@ callShadow: Shadow {
|
||||||
fallback: windowShadowFgFallback;
|
fallback: windowShadowFgFallback;
|
||||||
}
|
}
|
||||||
callPhotoSize: 180px;
|
callPhotoSize: 180px;
|
||||||
|
callPhotoSmallSize: 100px;
|
||||||
|
|
||||||
callOutgoingPreviewSize: size(340px, 180px);
|
callOutgoingPreviewSize: size(340px, 180px);
|
||||||
callOutgoingDefaultSize: size(160px, 110px);
|
callOutgoingDefaultSize: size(160px, 110px);
|
||||||
|
|
|
@ -15,6 +15,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "calls/calls_emoji_fingerprint.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/buttons.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/shadow.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)
|
Panel::Button::Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo) : Ui::RippleButton(parent, stFrom.button.ripple)
|
||||||
, _stFrom(&stFrom)
|
, _stFrom(&stFrom)
|
||||||
, _stTo(stTo) {
|
, _stTo(stTo) {
|
||||||
|
@ -312,8 +254,7 @@ Panel::Panel(not_null<Call*> call)
|
||||||
, _camera(this, st::callCameraToggle)
|
, _camera(this, st::callCameraToggle)
|
||||||
, _mute(this, st::callMuteToggle)
|
, _mute(this, st::callMuteToggle)
|
||||||
, _name(this, st::callName)
|
, _name(this, st::callName)
|
||||||
, _status(this, st::callStatus)
|
, _status(this, st::callStatus) {
|
||||||
, _signalBars(this, call, st::callPanelSignalBars) {
|
|
||||||
_decline->setDuration(st::callPanelDuration);
|
_decline->setDuration(st::callPanelDuration);
|
||||||
_cancel->setDuration(st::callPanelDuration);
|
_cancel->setDuration(st::callPanelDuration);
|
||||||
|
|
||||||
|
@ -341,9 +282,7 @@ void Panel::replaceCall(not_null<Call*> call) {
|
||||||
|
|
||||||
bool Panel::eventHook(QEvent *e) {
|
bool Panel::eventHook(QEvent *e) {
|
||||||
if (e->type() == QEvent::WindowDeactivate) {
|
if (e->type() == QEvent::WindowDeactivate) {
|
||||||
if (_call && _call->state() == State::Established) {
|
checkForInactiveHide();
|
||||||
hideDeactivated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return RpWidget::eventHook(e);
|
return RpWidget::eventHook(e);
|
||||||
}
|
}
|
||||||
|
@ -422,11 +361,29 @@ void Panel::reinitWithCall(Call *call) {
|
||||||
_callLifetime.destroy();
|
_callLifetime.destroy();
|
||||||
_call = call;
|
_call = call;
|
||||||
if (!_call) {
|
if (!_call) {
|
||||||
|
_outgoingVideoBubble = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_user = _call->user();
|
_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(
|
_call->mutedValue(
|
||||||
) | rpl::start_with_next([=](bool mute) {
|
) | rpl::start_with_next([=](bool mute) {
|
||||||
_mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr);
|
_mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr);
|
||||||
|
@ -444,53 +401,42 @@ void Panel::reinitWithCall(Call *call) {
|
||||||
stateChanged(state);
|
stateChanged(state);
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
rpl::merge(
|
_call->videoIncoming()->renderNextFrame(
|
||||||
_call->videoIncoming()->renderNextFrame(),
|
|
||||||
_call->videoOutgoing()->renderNextFrame()
|
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
setIncomingShown(!_call->videoIncoming()->frame({}).isNull());
|
setIncomingShown(!_call->videoIncoming()->frame({}).isNull());
|
||||||
update();
|
update();
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
_signalBars.create(
|
rpl::merge(
|
||||||
this,
|
_call->videoIncoming()->stateChanges(),
|
||||||
_call,
|
_call->videoOutgoing()->stateChanges()
|
||||||
st::callPanelSignalBars,
|
) | rpl::start_with_next([=] {
|
||||||
[=] { rtlupdate(signalBarsRect()); });
|
checkForInactiveShow();
|
||||||
|
}, _callLifetime);
|
||||||
|
|
||||||
_name->setText(_user->name);
|
_name->setText(_user->name);
|
||||||
updateStatusText(_call->state());
|
updateStatusText(_call->state());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::initLayout() {
|
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_MacAlwaysShowToolWindow);
|
||||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
setAttribute(Qt::WA_NoSystemBackground);
|
||||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
setAttribute(Qt::WA_TranslucentBackground);
|
||||||
|
|
||||||
initGeometry();
|
initGeometry();
|
||||||
|
|
||||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
_user->session().changes().peerUpdates(
|
_user->session().changes().peerUpdates(
|
||||||
UpdateFlag::Name | UpdateFlag::Photo
|
UpdateFlag::Name
|
||||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||||
// _user may change for the same Panel.
|
// _user may change for the same Panel.
|
||||||
return (_call != nullptr) && (update.peer == _user);
|
return (_call != nullptr) && (update.peer == _user);
|
||||||
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||||
if (update.flags & UpdateFlag::Name) {
|
|
||||||
_name->setText(_call->user()->name);
|
_name->setText(_call->user()->name);
|
||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
}
|
|
||||||
if (update.flags & UpdateFlag::Photo) {
|
|
||||||
processUserPhoto();
|
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
processUserPhoto();
|
|
||||||
|
|
||||||
_user->session().downloaderTaskFinished(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
refreshUserPhoto();
|
|
||||||
}, lifetime());
|
|
||||||
createDefaultCacheImage();
|
createDefaultCacheImage();
|
||||||
|
|
||||||
Ui::Platform::InitOnTopPanel(this);
|
Ui::Platform::InitOnTopPanel(this);
|
||||||
|
@ -543,6 +489,7 @@ void Panel::showControls() {
|
||||||
_cancel->setVisible(_cancel->toggled());
|
_cancel->setVisible(_cancel->toggled());
|
||||||
_name->setVisible(!_incomingShown);
|
_name->setVisible(!_incomingShown);
|
||||||
_status->setVisible(!_incomingShown);
|
_status->setVisible(!_incomingShown);
|
||||||
|
_userpic->setVisible(!_incomingShown);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::destroyDelayed() {
|
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() {
|
void Panel::initGeometry() {
|
||||||
const auto center = Core::App().getPointForCallPanelCenter();
|
const auto center = Core::App().getPointForCallPanelCenter();
|
||||||
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
|
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
|
||||||
|
@ -703,26 +566,23 @@ void Panel::createDefaultCacheImage() {
|
||||||
_cache = App::pixmapFromImageInPlace(std::move(cache));
|
_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) {
|
void Panel::resizeEvent(QResizeEvent *e) {
|
||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::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);
|
_name->moveToLeft((width() - _name->width()) / 2, _contentTop + st::callNameTop);
|
||||||
updateStatusGeometry();
|
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 controlsTop = _padding.top() + st::callControlsTop;
|
||||||
auto bothWidth = _answerHangupRedial->width() + st::callControlsSkip + st::callCancel.button.width;
|
auto bothWidth = _answerHangupRedial->width() + st::callControlsSkip + st::callCancel.button.width;
|
||||||
_decline->moveToLeft((width() - bothWidth) / 2, controlsTop);
|
_decline->moveToLeft((width() - bothWidth) / 2, controlsTop);
|
||||||
|
@ -777,7 +637,6 @@ void Panel::paintEvent(QPaintEvent *e) {
|
||||||
if (_useTransparency) {
|
if (_useTransparency) {
|
||||||
p.drawPixmapLeft(0, 0, width(), _cache);
|
p.drawPixmapLeft(0, 0, width(), _cache);
|
||||||
} else {
|
} else {
|
||||||
p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto);
|
|
||||||
auto callBgOpaque = st::callBg->c;
|
auto callBgOpaque = st::callBg->c;
|
||||||
callBgOpaque.setAlpha(255);
|
callBgOpaque.setAlpha(255);
|
||||||
p.fillRect(rect(), QBrush(callBgOpaque));
|
p.fillRect(rect(), QBrush(callBgOpaque));
|
||||||
|
@ -800,28 +659,6 @@ void Panel::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
_call->videoIncoming()->markFrameShown();
|
_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()) {
|
if (_signalBars->isDisplayed()) {
|
||||||
paintSignalBarsBg(p);
|
paintSignalBarsBg(p);
|
||||||
}
|
}
|
||||||
|
@ -920,9 +757,10 @@ bool Panel::tooltipWindowActive() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::stateChanged(State state) {
|
void Panel::stateChanged(State state) {
|
||||||
|
Expects(_call != nullptr);
|
||||||
|
|
||||||
updateStatusText(state);
|
updateStatusText(state);
|
||||||
|
|
||||||
if (_call) {
|
|
||||||
if ((state != State::HangingUp)
|
if ((state != State::HangingUp)
|
||||||
&& (state != State::Ended)
|
&& (state != State::Ended)
|
||||||
&& (state != State::EndedByOtherDevice)
|
&& (state != State::EndedByOtherDevice)
|
||||||
|
@ -951,20 +789,37 @@ void Panel::stateChanged(State state) {
|
||||||
fillFingerprint();
|
fillFingerprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (windowHandle()) {
|
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)) {
|
if ((state == State::Starting) || (state == State::WaitingIncoming)) {
|
||||||
Ui::Platform::ReInitOnTopPanel(this);
|
Ui::Platform::ReInitOnTopPanel(this);
|
||||||
} else {
|
} else {
|
||||||
Ui::Platform::DeInitOnTopPanel(this);
|
Ui::Platform::DeInitOnTopPanel(this);
|
||||||
}
|
}
|
||||||
|
checkForInactiveHide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
if (state == State::Established) {
|
|
||||||
if (!isActiveWindow()) {
|
|
||||||
hideDeactivated();
|
hideDeactivated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Panel::checkForInactiveShow() {
|
||||||
|
if (!_visible && hasActiveVideo()) {
|
||||||
|
toggleOpacityAnimation(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,27 +34,9 @@ struct CallSignalBars;
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
|
|
||||||
class SignalBars final : public Ui::RpWidget {
|
class Userpic;
|
||||||
public:
|
class SignalBars;
|
||||||
SignalBars(
|
class VideoBubble;
|
||||||
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 Panel final : public Ui::RpWidget, private Ui::AbstractTooltipShower {
|
class Panel final : public Ui::RpWidget, private Ui::AbstractTooltipShower {
|
||||||
|
|
||||||
|
@ -93,12 +75,7 @@ private:
|
||||||
void hideDeactivated();
|
void hideDeactivated();
|
||||||
void createBottomImage();
|
void createBottomImage();
|
||||||
void createDefaultCacheImage();
|
void createDefaultCacheImage();
|
||||||
void refreshCacheImageUserPhoto();
|
|
||||||
|
|
||||||
void processUserPhoto();
|
|
||||||
void refreshUserPhoto();
|
|
||||||
bool isGoodUserPhoto(PhotoData *photo);
|
|
||||||
void createUserpicCache(Image *image);
|
|
||||||
QRect signalBarsRect() const;
|
QRect signalBarsRect() const;
|
||||||
void paintSignalBarsBg(Painter &p);
|
void paintSignalBarsBg(Painter &p);
|
||||||
|
|
||||||
|
@ -115,10 +92,12 @@ private:
|
||||||
void destroyDelayed();
|
void destroyDelayed();
|
||||||
void setIncomingShown(bool shown);
|
void setIncomingShown(bool shown);
|
||||||
|
|
||||||
|
[[nodiscard]] bool hasActiveVideo() const;
|
||||||
|
void checkForInactiveHide();
|
||||||
|
void checkForInactiveShow();
|
||||||
|
|
||||||
Call *_call = nullptr;
|
Call *_call = nullptr;
|
||||||
not_null<UserData*> _user;
|
not_null<UserData*> _user;
|
||||||
std::shared_ptr<Data::CloudImageView> _userpic;
|
|
||||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
|
||||||
|
|
||||||
bool _useTransparency = true;
|
bool _useTransparency = true;
|
||||||
bool _incomingShown = false;
|
bool _incomingShown = false;
|
||||||
|
@ -142,7 +121,9 @@ private:
|
||||||
object_ptr<Ui::IconButton> _mute;
|
object_ptr<Ui::IconButton> _mute;
|
||||||
object_ptr<Ui::FlatLabel> _name;
|
object_ptr<Ui::FlatLabel> _name;
|
||||||
object_ptr<Ui::FlatLabel> _status;
|
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;
|
std::vector<EmojiPtr> _fingerprint;
|
||||||
QRect _fingerprintArea;
|
QRect _fingerprintArea;
|
||||||
|
|
||||||
|
@ -150,9 +131,6 @@ private:
|
||||||
base::Timer _updateOuterRippleTimer;
|
base::Timer _updateOuterRippleTimer;
|
||||||
|
|
||||||
bool _visible = false;
|
bool _visible = false;
|
||||||
QPixmap _userPhoto;
|
|
||||||
PhotoId _userPhotoId = 0;
|
|
||||||
bool _userPhotoFull = false;
|
|
||||||
|
|
||||||
Ui::Animations::Simple _opacityAnimation;
|
Ui::Animations::Simple _opacityAnimation;
|
||||||
QPixmap _animationCache;
|
QPixmap _animationCache;
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "calls/calls_call.h"
|
#include "calls/calls_call.h"
|
||||||
#include "calls/calls_instance.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_user.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue