tdesktop/Telegram/SourceFiles/calls/calls_panel.cpp

873 lines
26 KiB
C++
Raw Normal View History

/*
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_panel.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
2019-09-16 11:14:06 +00:00
#include "data/data_file_origin.h"
2020-05-25 14:16:04 +00:00
#include "data/data_photo_media.h"
2020-05-28 14:32:10 +00:00
#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"
2020-08-12 13:35:31 +00:00
#include "ui/widgets/window.h"
#include "ui/effects/ripple_animation.h"
#include "ui/image/image.h"
#include "ui/wrap/fade_wrap.h"
2019-09-16 11:14:06 +00:00
#include "ui/platform/ui_platform_utility.h"
#include "ui/empty_userpic.h"
#include "ui/emoji_config.h"
#include "core/application.h"
#include "mainwindow.h"
2017-04-13 08:27:10 +00:00
#include "lang/lang_keys.h"
2019-07-24 11:45:24 +00:00
#include "main/main_session.h"
#include "apiwrap.h"
#include "platform/platform_specific.h"
#include "base/platform/base_platform_info.h"
#include "window/main_window.h"
#include "layout.h"
#include "app.h"
2020-07-31 14:36:35 +00:00
#include "webrtc/webrtc_video_track.h"
2019-09-16 11:14:06 +00:00
#include "styles/style_calls.h"
#include "styles/style_history.h"
#ifdef Q_OS_WIN
#include "ui/platform/win/ui_window_title_win.h"
#endif // Q_OS_WIN
2019-09-04 07:19:15 +00:00
#include <QtWidgets/QDesktopWidget>
#include <QtWidgets/QApplication>
2020-08-12 16:58:24 +00:00
#include <QtGui/QWindow>
2019-09-04 07:19:15 +00:00
namespace Calls {
namespace {
constexpr auto kTooltipShowTimeoutMs = 1000;
} // namespace
2020-08-12 13:35:31 +00:00
class Panel::Button final : public Ui::RippleButton {
public:
Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo = nullptr);
void setProgress(float64 progress);
void setOuterValue(float64 value);
void setIconOverride(const style::icon *iconOverride);
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(State was, StateChangeSource source) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
private:
QPoint iconPosition(not_null<const style::CallButton*> st) const;
void mixIconMasks();
not_null<const style::CallButton*> _stFrom;
const style::CallButton *_stTo = nullptr;
float64 _progress = 0.;
const style::icon *_iconOverride = nullptr;
QImage _bgMask, _bg;
QPixmap _bgFrom, _bgTo;
QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed;
float64 _outerValue = 0.;
2019-04-02 09:13:30 +00:00
Ui::Animations::Simple _outerAnimation;
};
2020-08-12 13:35:31 +00:00
Panel::Button::Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo)
: Ui::RippleButton(parent, stFrom.button.ripple)
, _stFrom(&stFrom)
, _stTo(stTo) {
resize(_stFrom->button.width, _stFrom->button.height);
_bgMask = prepareRippleMask();
_bgFrom = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stFrom->bg));
if (_stTo) {
Assert(_stFrom->button.width == _stTo->button.width);
Assert(_stFrom->button.height == _stTo->button.height);
Assert(_stFrom->button.rippleAreaPosition == _stTo->button.rippleAreaPosition);
Assert(_stFrom->button.rippleAreaSize == _stTo->button.rippleAreaSize);
_bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_bg.setDevicePixelRatio(cRetinaFactor());
_bgTo = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stTo->bg));
_iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconMixedMask.setDevicePixelRatio(cRetinaFactor());
_iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconFrom.setDevicePixelRatio(cRetinaFactor());
_iconFrom.fill(Qt::black);
{
Painter p(&_iconFrom);
p.drawImage((_stFrom->button.rippleAreaSize - _stFrom->button.icon.width()) / 2, (_stFrom->button.rippleAreaSize - _stFrom->button.icon.height()) / 2, _stFrom->button.icon.instance(Qt::white));
}
_iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconTo.setDevicePixelRatio(cRetinaFactor());
_iconTo.fill(Qt::black);
{
Painter p(&_iconTo);
p.drawImage((_stTo->button.rippleAreaSize - _stTo->button.icon.width()) / 2, (_stTo->button.rippleAreaSize - _stTo->button.icon.height()) / 2, _stTo->button.icon.instance(Qt::white));
}
_iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
_iconMixed.setDevicePixelRatio(cRetinaFactor());
}
}
void Panel::Button::setOuterValue(float64 value) {
if (_outerValue != value) {
_outerAnimation.start([this] {
if (_progress == 0. || _progress == 1.) {
update();
}
}, _outerValue, value, Call::kSoundSampleMs);
_outerValue = value;
}
}
void Panel::Button::setIconOverride(const style::icon *iconOverride) {
_iconOverride = iconOverride;
update();
}
void Panel::Button::setProgress(float64 progress) {
_progress = progress;
update();
}
void Panel::Button::paintEvent(QPaintEvent *e) {
Painter p(this);
auto bgPosition = myrtlpoint(_stFrom->button.rippleAreaPosition);
auto paintFrom = (_progress == 0.) || !_stTo;
auto paintTo = !paintFrom && (_progress == 1.);
2019-04-02 09:13:30 +00:00
auto outerValue = _outerAnimation.value(_outerValue);
if (outerValue > 0.) {
auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress);
auto outerPixels = outerValue * outerRadius;
auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize));
outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels));
PainterHighQualityEnabler hq(p);
if (paintFrom) {
p.setBrush(_stFrom->outerBg);
} else if (paintTo) {
p.setBrush(_stTo->outerBg);
} else {
p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress));
}
p.setPen(Qt::NoPen);
p.drawEllipse(outerRect);
}
if (paintFrom) {
p.drawPixmap(bgPosition, _bgFrom);
} else if (paintTo) {
p.drawPixmap(bgPosition, _bgTo);
} else {
style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg);
p.drawImage(bgPosition, _bg);
}
auto rippleColorInterpolated = QColor();
auto rippleColorOverride = &rippleColorInterpolated;
if (paintFrom) {
rippleColorOverride = nullptr;
} else if (paintTo) {
rippleColorOverride = &_stTo->button.ripple.color->c;
} else {
rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
}
2019-04-02 09:13:30 +00:00
paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride);
auto positionFrom = iconPosition(_stFrom);
if (paintFrom) {
const auto icon = _iconOverride ? _iconOverride : &_stFrom->button.icon;
icon->paint(p, positionFrom, width());
} else {
auto positionTo = iconPosition(_stTo);
if (paintTo) {
_stTo->button.icon.paint(p, positionTo, width());
} else {
mixIconMasks();
style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed);
p.drawImage(myrtlpoint(_stFrom->button.rippleAreaPosition), _iconMixed);
}
}
}
QPoint Panel::Button::iconPosition(not_null<const style::CallButton*> st) const {
auto result = st->button.iconPosition;
if (result.x() < 0) {
result.setX((width() - st->button.icon.width()) / 2);
}
if (result.y() < 0) {
result.setY((height() - st->button.icon.height()) / 2);
}
return result;
}
void Panel::Button::mixIconMasks() {
_iconMixedMask.fill(Qt::black);
Painter p(&_iconMixedMask);
PainterHighQualityEnabler hq(p);
auto paintIconMask = [this, &p](const QImage &mask, float64 angle) {
auto skipFrom = _stFrom->button.rippleAreaSize / 2;
p.translate(skipFrom, skipFrom);
p.rotate(angle);
p.translate(-skipFrom, -skipFrom);
p.drawImage(0, 0, mask);
};
p.save();
paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress);
p.restore();
p.setOpacity(_progress);
paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress));
}
void Panel::Button::onStateChanged(State was, StateChangeSource source) {
RippleButton::onStateChanged(was, source);
auto over = isOver();
auto wasOver = static_cast<bool>(was & StateFlag::Over);
if (over != wasOver) {
update();
}
}
QPoint Panel::Button::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
}
QImage Panel::Button::prepareRippleMask() const {
return Ui::RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize));
}
Panel::Panel(not_null<Call*> call)
2020-08-12 13:35:31 +00:00
: _call(call)
, _user(call->user())
2020-08-12 13:35:31 +00:00
, _window(std::make_unique<Ui::Window>(Core::App().getModalParent()))
#ifdef Q_OS_WIN
, _controls(std::make_unique<Ui::Platform::TitleControls>(
_window.get(),
[=](bool maximized) { toggleFullScreen(maximized); }))
#endif // Q_OS_WIN
, _bodySt(&st::callBodyLayout)
2020-08-12 13:35:31 +00:00
, _answerHangupRedial(widget(), st::callAnswer, &st::callHangup)
, _decline(widget(), object_ptr<Button>(widget(), st::callHangup))
, _cancel(widget(), object_ptr<Button>(widget(), st::callCancel))
, _camera(widget(), st::callCameraToggle)
, _mute(widget(), st::callMuteToggle)
, _name(widget(), st::callName)
, _status(widget(), st::callStatus) {
_decline->setDuration(st::callPanelDuration);
_cancel->setDuration(st::callPanelDuration);
2020-08-12 13:35:31 +00:00
initWindow();
initWidget();
initControls();
initLayout();
showAndActivate();
}
2020-05-25 14:16:04 +00:00
Panel::~Panel() = default;
void Panel::showAndActivate() {
2020-08-12 13:35:31 +00:00
_window->raise();
_window->setWindowState(_window->windowState() | Qt::WindowActive);
_window->activateWindow();
_window->setFocus();
}
void Panel::replaceCall(not_null<Call*> call) {
2020-08-04 09:06:48 +00:00
reinitWithCall(call);
updateControlsGeometry();
}
2020-08-12 13:35:31 +00:00
void Panel::initWindow() {
_window->setWindowIcon(
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
_window->setTitle(u" "_q);
2020-08-12 16:58:24 +00:00
_window->setTitleStyle(st::callTitle);
2020-08-12 13:35:31 +00:00
#ifdef Q_OS_WIN
_controls->setStyle(st::callTitle);
#endif // Q_OS_WIN
2020-08-12 13:35:31 +00:00
_window->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::Close) {
handleClose();
2020-08-12 16:58:24 +00:00
} else if (e->type() == QEvent::KeyPress) {
if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
&& _window->isFullScreen()) {
_window->showNormal();
}
2020-08-12 13:35:31 +00:00
}
}, _window->lifetime());
_window->setBodyTitleArea([=](QPoint widgetPoint) {
2020-08-13 09:02:15 +00:00
using Flag = Ui::WindowTitleHitTestFlag;
if (!widget()->rect().contains(widgetPoint)) {
return Flag::None | Flag(0);
}
#ifdef Q_OS_WIN
if (_controls->geometry().contains(widgetPoint)) {
return Flag::None | Flag(0);
}
#endif // Q_OS_WIN
2020-08-12 13:35:31 +00:00
const auto buttonWidth = st::callCancel.button.width;
const auto buttonsWidth = buttonWidth * 4;
2020-08-12 16:58:24 +00:00
const auto inControls = _fingerprintArea.contains(widgetPoint)
|| QRect(
2020-08-12 13:35:31 +00:00
(widget()->width() - buttonsWidth) / 2,
_answerHangupRedial->y(),
buttonsWidth,
_answerHangupRedial->height()).contains(widgetPoint)
2020-08-12 16:58:24 +00:00
|| (!_outgoingPreviewInBody
&& _outgoingVideoBubble->geometry().contains(widgetPoint));
return inControls
? Flag::None
: (Flag::Move | Flag::FullScreen);
2020-08-12 13:35:31 +00:00
});
2020-08-12 16:58:24 +00:00
#ifdef Q_OS_WIN
// On Windows we replace snap-to-top maximizing with fullscreen.
//
// We have to switch first to showNormal, so that showFullScreen
// will remember correct normal window geometry and next showNormal
// will show it instead of a moving maximized window.
//
// We have to do it in InvokeQueued, otherwise it still captures
// the maximized window geometry and saves it.
//
// I couldn't find a less glitchy way to do that *sigh*.
const auto object = _window->windowHandle();
const auto signal = &QWindow::windowStateChanged;
QObject::connect(object, signal, [=](Qt::WindowState state) {
if (state == Qt::WindowMaximized) {
InvokeQueued(object, [=] {
_window->showNormal();
_window->showFullScreen();
});
}
});
#endif // Q_OS_WIN
}
2020-08-12 13:35:31 +00:00
void Panel::initWidget() {
widget()->setMouseTracking(true);
widget()->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
paint(clip);
}, widget()->lifetime());
widget()->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::MouseMove) {
handleMouseMove(static_cast<QMouseEvent*>(e.get()));
} else if (e->type() == QEvent::Resize) {
updateControlsGeometry();
}
}, widget()->lifetime());
}
void Panel::initControls() {
_hangupShown = (_call->type() == Type::Outgoing);
2020-07-16 16:23:55 +00:00
_mute->setClickedCallback([=] {
2017-05-04 13:32:56 +00:00
if (_call) {
2020-07-16 16:23:55 +00:00
_call->setMuted(!_call->muted());
2017-05-04 13:32:56 +00:00
}
});
2020-07-16 16:23:55 +00:00
_camera->setClickedCallback([=] {
if (_call) {
2020-08-05 12:11:18 +00:00
_call->videoOutgoing()->setState(
2020-08-11 09:59:48 +00:00
(_call->videoOutgoing()->state() == Webrtc::VideoState::Active)
? Webrtc::VideoState::Inactive
: Webrtc::VideoState::Active);
2020-07-16 16:23:55 +00:00
}
});
_updateDurationTimer.setCallback([this] {
if (_call) {
updateStatusText(_call->state());
}
});
_updateOuterRippleTimer.setCallback([this] {
if (_call) {
_answerHangupRedial->setOuterValue(_call->getWaitingSoundPeakValue());
} else {
_answerHangupRedial->setOuterValue(0.);
_updateOuterRippleTimer.cancel();
}
});
_answerHangupRedial->setClickedCallback([this] {
if (!_call || _hangupShownProgress.animating()) {
return;
}
auto state = _call->state();
if (state == State::Busy) {
_call->redial();
} else if (_call->isIncomingWaiting()) {
_call->answer();
} else {
_call->hangup();
}
});
auto hangupCallback = [this] {
if (_call) {
_call->hangup();
}
};
_decline->entity()->setClickedCallback(hangupCallback);
_cancel->entity()->setClickedCallback(hangupCallback);
2020-08-04 09:06:48 +00:00
reinitWithCall(_call);
_decline->finishAnimating();
_cancel->finishAnimating();
2020-08-04 09:06:48 +00:00
}
2020-08-04 09:06:48 +00:00
void Panel::setIncomingShown(bool shown) {
if (_incomingShown == shown) {
return;
}
_incomingShown = shown;
2020-08-12 13:35:31 +00:00
showControls();
}
2020-08-04 09:06:48 +00:00
void Panel::reinitWithCall(Call *call) {
_callLifetime.destroy();
_call = call;
if (!_call) {
_outgoingVideoBubble = nullptr;
2020-08-04 09:06:48 +00:00
return;
}
_user = _call->user();
_signalBars.create(
2020-08-12 13:35:31 +00:00
widget(),
_call,
st::callPanelSignalBars,
2020-08-12 13:35:31 +00:00
[=] { widget()->rtlupdate(signalBarsRect()); });
auto remoteMuted = _call->remoteAudioStateValue(
) | rpl::map([=](Call::RemoteAudioState state) {
return (state == Call::RemoteAudioState::Muted);
});
2020-08-12 13:35:31 +00:00
_userpic = std::make_unique<Userpic>(
widget(),
_user,
std::move(remoteMuted));
_outgoingVideoBubble = std::make_unique<VideoBubble>(
2020-08-12 13:35:31 +00:00
widget(),
_call->videoOutgoing());
2020-08-04 09:06:48 +00:00
_call->mutedValue(
) | rpl::start_with_next([=](bool mute) {
_mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr);
}, _callLifetime);
2020-08-05 12:11:18 +00:00
_call->videoOutgoing()->stateValue(
2020-08-11 09:59:48 +00:00
) | rpl::start_with_next([=](Webrtc::VideoState state) {
_camera->setIconOverride((state == Webrtc::VideoState::Active)
2020-08-05 12:11:18 +00:00
? nullptr
: &st::callNoCameraIcon);
2020-08-04 09:06:48 +00:00
}, _callLifetime);
2017-05-04 13:32:56 +00:00
2020-06-25 17:57:36 +00:00
_call->stateValue(
) | rpl::start_with_next([=](State state) {
stateChanged(state);
2020-08-04 09:06:48 +00:00
}, _callLifetime);
2020-08-12 16:58:24 +00:00
rpl::merge(
_call->videoIncoming()->renderNextFrame(),
_call->videoOutgoing()->renderNextFrame()
2020-08-04 09:06:48 +00:00
) | rpl::start_with_next([=] {
setIncomingShown(!_call->videoIncoming()->frame({}).isNull());
2020-08-12 13:35:31 +00:00
widget()->update();
}, _callLifetime);
2018-05-27 08:24:47 +00:00
rpl::combine(
_call->stateValue(),
_call->videoOutgoing()->renderNextFrame()
) | rpl::start_with_next([=](State state, auto) {
if (state != State::Ended
&& state != State::EndedByOtherDevice
&& state != State::Failed
&& state != State::FailedHangingUp
&& state != State::HangingUp) {
refreshOutgoingPreviewInBody(state);
}
}, _callLifetime);
_name->setText(_user->name);
updateStatusText(_call->state());
}
void Panel::initLayout() {
initGeometry();
using UpdateFlag = Data::PeerUpdate::Flag;
_user->session().changes().peerUpdates(
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) {
_name->setText(_call->user()->name);
updateControlsGeometry();
2020-08-12 13:35:31 +00:00
}, widget()->lifetime());
#ifdef Q_OS_WIN
_controls->raise();
#endif // Q_OS_WIN
2017-05-04 13:32:56 +00:00
}
void Panel::showControls() {
Expects(_call != nullptr);
2020-05-28 14:32:10 +00:00
2020-08-12 13:35:31 +00:00
widget()->showChildren();
_decline->setVisible(_decline->toggled());
_cancel->setVisible(_cancel->toggled());
2020-08-04 09:06:48 +00:00
_name->setVisible(!_incomingShown);
_status->setVisible(!_incomingShown);
_userpic->setVisible(!_incomingShown);
}
2020-08-12 13:35:31 +00:00
void Panel::hideBeforeDestroy() {
_window->hide();
2020-08-04 09:06:48 +00:00
reinitWithCall(nullptr);
2017-05-04 13:32:56 +00:00
}
void Panel::initGeometry() {
2020-05-14 16:27:51 +00:00
const auto center = Core::App().getPointForCallPanelCenter();
2020-08-12 13:35:31 +00:00
const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
_window->setGeometry(initRect.translated(center - initRect.center()));
_window->setMinimumSize({ st::callWidthMin, st::callHeightMin });
_window->show();
updateControlsGeometry();
}
void Panel::refreshOutgoingPreviewInBody(State state) {
const auto inBody = (state != State::Established)
2020-08-11 09:59:48 +00:00
&& (_call->videoOutgoing()->state() != Webrtc::VideoState::Inactive)
&& !_call->videoOutgoing()->frameSize().isEmpty();
if (_outgoingPreviewInBody == inBody) {
return;
}
_outgoingPreviewInBody = inBody;
_bodySt = inBody ? &st::callBodyWithPreview : &st::callBodyLayout;
updateControlsGeometry();
}
void Panel::toggleFullScreen(bool fullscreen) {
if (fullscreen) {
_window->showFullScreen();
} else {
_window->showNormal();
}
}
2020-08-12 13:35:31 +00:00
void Panel::updateFingerprintGeometry() {
2020-08-13 10:39:55 +00:00
auto realSize = Ui::Emoji::GetSizeNormal();
2020-08-12 13:35:31 +00:00
auto size = realSize / cIntRetinaFactor();
auto count = _fingerprint.size();
auto rectWidth = count * size + (count - 1) * st::callFingerprintSkip;
auto rectHeight = size;
auto left = (widget()->width() - rectWidth) / 2;
_fingerprintArea = QRect(
left,
st::callFingerprintTop + st::callFingerprintPadding.top(),
rectWidth,
rectHeight
).marginsAdded(st::callFingerprintPadding);
_fingerprintHeight = st::callFingerprintTop + _fingerprintArea.height() + st::callFingerprintBottom;
}
2020-08-12 13:35:31 +00:00
void Panel::updateControlsGeometry() {
if (widget()->width() < st::callWidthMin
|| widget()->height() < st::callHeightMin) {
return;
}
2020-08-12 13:35:31 +00:00
updateFingerprintGeometry();
const auto innerHeight = widget()->height();
const auto availableTop = _fingerprintHeight;
const auto available = widget()->height()
- st::callBottomControlsHeight
- availableTop;
const auto bodyPreviewSizeMax = st::callOutgoingPreviewMin
+ ((st::callOutgoingPreview
- st::callOutgoingPreviewMin)
* (innerHeight - st::callHeightMin)
/ (st::callHeight - st::callHeightMin));
const auto bodyPreviewSize = QSize(
std::min(bodyPreviewSizeMax.width(), st::callOutgoingPreviewMax.width()),
std::min(bodyPreviewSizeMax.height(), st::callOutgoingPreviewMax.height()));
const auto contentHeight = _bodySt->height
+ (_outgoingPreviewInBody ? bodyPreviewSize.height() : 0);
const auto remainingHeight = available - contentHeight;
const auto skipHeight = remainingHeight
/ (_outgoingPreviewInBody ? 3 : 2);
_bodyTop = availableTop + skipHeight;
_buttonsTop = availableTop + available;
const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
_userpic->setGeometry(
2020-08-12 13:35:31 +00:00
(widget()->width() - _bodySt->photoSize) / 2,
_bodyTop + _bodySt->photoTop,
_bodySt->photoSize);
_name->moveToLeft(
2020-08-12 13:35:31 +00:00
(widget()->width() - _name->width()) / 2,
_bodyTop + _bodySt->nameTop);
updateStatusGeometry();
if (_outgoingPreviewInBody) {
_outgoingVideoBubble->updateGeometry(
VideoBubble::DragMode::None,
QRect(
2020-08-12 13:35:31 +00:00
(widget()->width() - bodyPreviewSize.width()) / 2,
previewTop,
bodyPreviewSize.width(),
bodyPreviewSize.height()));
} else {
updateOutgoingVideoBubbleGeometry();
}
auto bothWidth = _answerHangupRedial->width() + st::callCancel.button.width;
2020-08-12 13:35:31 +00:00
_decline->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop);
_cancel->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop);
updateHangupGeometry();
2018-05-27 08:24:47 +00:00
const auto skip = st::callSignalMargin + st::callSignalPadding;
const auto delta = (_signalBars->width() - _signalBars->height());
2020-08-12 13:35:31 +00:00
_signalBars->moveToLeft(skip, skip + delta / 2);
}
void Panel::updateOutgoingVideoBubbleGeometry() {
Expects(!_outgoingPreviewInBody);
const auto size = st::callOutgoingDefaultSize;
_outgoingVideoBubble->updateGeometry(
VideoBubble::DragMode::SnapToCorners,
2020-08-12 13:35:31 +00:00
widget()->rect(),
size);
}
void Panel::updateHangupGeometry() {
auto singleWidth = _answerHangupRedial->width();
auto bothWidth = singleWidth + st::callCancel.button.width;
2020-08-12 13:35:31 +00:00
auto rightFrom = (widget()->width() - bothWidth) / 2;
auto rightTo = (widget()->width() - singleWidth) / 2;
2019-04-02 09:13:30 +00:00
auto hangupProgress = _hangupShownProgress.value(_hangupShown ? 1. : 0.);
auto hangupRight = anim::interpolate(rightFrom, rightTo, hangupProgress);
_answerHangupRedial->moveToRight(hangupRight, _buttonsTop);
_answerHangupRedial->setProgress(hangupProgress);
_mute->moveToRight(hangupRight - _mute->width(), _buttonsTop);
_camera->moveToLeft(hangupRight - _mute->width(), _buttonsTop);
}
void Panel::updateStatusGeometry() {
2020-08-12 13:35:31 +00:00
_status->moveToLeft(
(widget()->width() - _status->width()) / 2,
_bodyTop + _bodySt->statusTop);
}
2020-08-12 13:35:31 +00:00
void Panel::paint(QRect clip) {
Painter p(widget());
2017-05-04 13:32:56 +00:00
2020-08-12 13:35:31 +00:00
p.fillRect(clip, st::callBgOpaque);
2020-07-31 14:36:35 +00:00
const auto incomingFrame = _call
2020-08-11 09:59:48 +00:00
? _call->videoIncoming()->frame(Webrtc::FrameRequest())
2020-07-31 14:36:35 +00:00
: QImage();
if (!incomingFrame.isNull()) {
2020-08-12 13:35:31 +00:00
const auto to = widget()->rect();
p.save();
p.setClipRect(to);
2020-08-04 09:06:48 +00:00
const auto big = incomingFrame.size().scaled(to.size(), Qt::KeepAspectRatio);
2020-07-31 14:36:35 +00:00
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), incomingFrame);
p.restore();
}
_call->videoIncoming()->markFrameShown();
2018-05-27 08:24:47 +00:00
if (_signalBars->isDisplayed()) {
paintSignalBarsBg(p);
}
if (!_fingerprint.empty() && clip.intersects(_fingerprintArea)) {
const auto radius = _fingerprintArea.height() / 2;
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::callBgButton);
p.setPen(Qt::NoPen);
p.drawRoundedRect(_fingerprintArea, radius, radius);
2020-08-13 10:39:55 +00:00
const auto realSize = Ui::Emoji::GetSizeNormal();
const auto size = realSize / cIntRetinaFactor();
auto left = _fingerprintArea.left() + st::callFingerprintPadding.left();
const auto top = _fingerprintArea.top() + st::callFingerprintPadding.top();
for (const auto emoji : _fingerprint) {
Ui::Emoji::Draw(p, emoji, realSize, left, top);
left += st::callFingerprintSkip + size;
}
}
}
2018-05-27 08:24:47 +00:00
QRect Panel::signalBarsRect() const {
const auto size = 2 * st::callSignalPadding + _signalBars->width();
return QRect(
2020-08-12 13:35:31 +00:00
st::callSignalMargin,
st::callSignalMargin,
2018-05-27 08:24:47 +00:00
size,
size);
}
void Panel::paintSignalBarsBg(Painter &p) {
App::roundRect(
p,
signalBarsRect(),
st::callBgButton,
2018-05-27 08:24:47 +00:00
ImageRoundRadius::Small);
}
2020-08-12 13:35:31 +00:00
void Panel::handleClose() {
2017-05-15 08:17:59 +00:00
if (_call) {
_call->hangup();
}
}
2020-08-12 13:35:31 +00:00
void Panel::handleMouseMove(not_null<QMouseEvent*> e) {
if (_fingerprintArea.contains(e->pos())) {
Ui::Tooltip::Show(kTooltipShowTimeoutMs, this);
} else {
Ui::Tooltip::Hide();
}
}
2020-08-12 13:35:31 +00:00
not_null<Ui::RpWidget*> Panel::widget() const {
return _window->body();
}
QString Panel::tooltipText() const {
return tr::lng_call_fingerprint_tooltip(tr::now, lt_user, _user->name);
}
QPoint Panel::tooltipPos() const {
return QCursor::pos();
}
bool Panel::tooltipWindowActive() const {
2020-08-12 13:35:31 +00:00
return _window->isActiveWindow();
}
void Panel::stateChanged(State state) {
Expects(_call != nullptr);
updateStatusText(state);
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,
2020-08-12 13:35:31 +00:00
_window->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();
2017-05-04 13:32:56 +00:00
}
}
}
void Panel::fillFingerprint() {
2017-05-04 13:32:56 +00:00
Expects(_call != nullptr);
2020-08-12 13:35:31 +00:00
_fingerprint = ComputeEmojiFingerprint(_call);
updateControlsGeometry();
2020-08-12 13:35:31 +00:00
widget()->update();
}
void Panel::updateStatusText(State state) {
auto statusText = [this, state]() -> QString {
switch (state) {
case State::Starting:
case State::WaitingInit:
2019-06-19 15:09:03 +00:00
case State::WaitingInitAck: return tr::lng_call_status_connecting(tr::now);
case State::Established: {
if (_call) {
auto durationMs = _call->getDurationMs();
auto durationSeconds = durationMs / 1000;
startDurationUpdateTimer(durationMs);
return formatDurationText(durationSeconds);
}
2019-06-19 15:09:03 +00:00
return tr::lng_call_status_ended(tr::now);
} break;
case State::FailedHangingUp:
2019-06-19 15:09:03 +00:00
case State::Failed: return tr::lng_call_status_failed(tr::now);
case State::HangingUp: return tr::lng_call_status_hanging(tr::now);
case State::Ended:
2019-06-19 15:09:03 +00:00
case State::EndedByOtherDevice: return tr::lng_call_status_ended(tr::now);
case State::ExchangingKeys: return tr::lng_call_status_exchanging(tr::now);
case State::Waiting: return tr::lng_call_status_waiting(tr::now);
case State::Requesting: return tr::lng_call_status_requesting(tr::now);
case State::WaitingIncoming: return tr::lng_call_status_incoming(tr::now);
case State::Ringing: return tr::lng_call_status_ringing(tr::now);
case State::Busy: return tr::lng_call_status_busy(tr::now);
}
Unexpected("State in stateChanged()");
};
_status->setText(statusText());
updateStatusGeometry();
}
void Panel::startDurationUpdateTimer(crl::time currentDuration) {
auto msTillNextSecond = 1000 - (currentDuration % 1000);
_updateDurationTimer.callOnce(msTillNextSecond + 5);
}
} // namespace Calls