2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 06:26:18 +00:00

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

@@ -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);
}
}