/* 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" #include "data/data_file_origin.h" #include "data/data_photo_media.h" #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" #include "ui/widgets/window.h" #include "ui/effects/ripple_animation.h" #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/padding_wrap.h" #include "ui/platform/ui_platform_utility.h" #include "ui/toast/toast.h" #include "ui/empty_userpic.h" #include "ui/emoji_config.h" #include "core/application.h" #include "mainwindow.h" #include "lang/lang_keys.h" #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 "app.h" #include "webrtc/webrtc_video_track.h" #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 #include #include #include namespace Calls { namespace { #if defined Q_OS_MAC && !defined OS_MAC_OLD #define USE_OPENGL_OVERLAY_WIDGET #endif // Q_OS_MAC && !OS_MAC_OLD #ifdef USE_OPENGL_OVERLAY_WIDGET using IncomingParent = Ui::RpWidgetWrap; #else // USE_OPENGL_OVERLAY_WIDGET using IncomingParent = Ui::RpWidget; #endif // USE_OPENGL_OVERLAY_WIDGET } // namespace class Panel::Incoming final : public IncomingParent { public: Incoming( not_null parent, not_null track); private: void paintEvent(QPaintEvent *e) override; void initBottomShadow(); void fillTopShadow(QPainter &p); void fillBottomShadow(QPainter &p); const not_null _track; QPixmap _bottomShadow; }; Panel::Incoming::Incoming( not_null parent, not_null track) : IncomingParent(parent) , _track(track) { initBottomShadow(); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_TransparentForMouseEvents); } void Panel::Incoming::paintEvent(QPaintEvent *e) { QPainter p(this); const auto frame = _track->frame(Webrtc::FrameRequest()); if (frame.isNull()) { p.fillRect(e->rect(), Qt::black); } else { auto hq = PainterHighQualityEnabler(p); p.drawImage(rect(), frame); fillBottomShadow(p); fillTopShadow(p); } _track->markFrameShown(); } void Panel::Incoming::initBottomShadow() { auto image = QImage( QSize(1, st::callBottomShadowSize) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); const auto colorFrom = uint32(0); const auto colorTill = uint32(74); const auto rows = image.height(); const auto step = (uint64(colorTill - colorFrom) << 32) / rows; auto accumulated = uint64(); auto bytes = image.bits(); for (auto y = 0; y != rows; ++y) { accumulated += step; const auto color = (colorFrom + uint32(accumulated >> 32)) << 24; for (auto x = 0; x != image.width(); ++x) { *(reinterpret_cast(bytes) + x) = color; } bytes += image.bytesPerLine(); } _bottomShadow = Images::PixmapFast(std::move(image)); } void Panel::Incoming::fillTopShadow(QPainter &p) { #ifdef Q_OS_WIN const auto width = parentWidget()->width(); const auto position = QPoint(width - st::callTitleShadow.width(), 0); const auto shadowArea = QRect( position, st::callTitleShadow.size()); const auto fill = shadowArea.intersected(geometry()).translated(-pos()); if (fill.isEmpty()) { return; } p.save(); p.setClipRect(fill); st::callTitleShadow.paint(p, position - pos(), width); p.restore(); #endif // Q_OS_WIN } void Panel::Incoming::fillBottomShadow(QPainter &p) { const auto shadowArea = QRect( 0, parentWidget()->height() - st::callBottomShadowSize, parentWidget()->width(), st::callBottomShadowSize); const auto fill = shadowArea.intersected(geometry()).translated(-pos()); if (fill.isEmpty()) { return; } const auto factor = cIntRetinaFactor(); p.drawPixmap( fill, _bottomShadow, QRect( 0, factor * (fill.y() - shadowArea.translated(-pos()).y()), factor, factor * fill.height())); } 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 setText(rpl::producer text); 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 st) const; void mixIconMasks(); not_null _stFrom; const style::CallButton *_stTo = nullptr; float64 _progress = 0.; object_ptr _label = { nullptr }; QImage _bgMask, _bg; QPixmap _bgFrom, _bgTo; QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; float64 _outerValue = 0.; Ui::Animations::Simple _outerAnimation; }; 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::setText(rpl::producer text) { _label.create(this, std::move(text), _stFrom->label); _label->show(); rpl::combine( sizeValue(), _label->sizeValue() ) | rpl::start_with_next([=](QSize my, QSize label) { _label->moveToLeft( (my.width() - label.width()) / 2, my.height() - label.height(), my.width()); }, _label->lifetime()); } 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.); 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); } paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride); auto positionFrom = iconPosition(_stFrom); if (paintFrom) { const auto icon = &_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 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(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(call) , _user(call->user()) , _window(std::make_unique(Core::App().getModalParent())) #ifdef Q_OS_WIN , _controls(std::make_unique( _window.get(), st::callTitle, [=](bool maximized) { toggleFullScreen(maximized); })) #endif // Q_OS_WIN , _bodySt(&st::callBodyLayout) , _answerHangupRedial(widget(), st::callAnswer, &st::callHangup) , _decline(widget(), object_ptr