mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-22 00:57:09 +02:00
Extract Calls::Userpic and Calls::VideoBubble.
This commit is contained in:
parent
95de762529
commit
d4b8fa70a7
12 changed files with 703 additions and 277 deletions
|
@ -324,8 +324,14 @@ PRIVATE
|
|||
calls/calls_instance.h
|
||||
calls/calls_panel.cpp
|
||||
calls/calls_panel.h
|
||||
calls/calls_signal_bars.cpp
|
||||
calls/calls_signal_bars.h
|
||||
calls/calls_top_bar.cpp
|
||||
calls/calls_top_bar.h
|
||||
calls/calls_userpic.cpp
|
||||
calls/calls_userpic.h
|
||||
calls/calls_video_bubble.cpp
|
||||
calls/calls_video_bubble.h
|
||||
chat_helpers/bot_keyboard.cpp
|
||||
chat_helpers/bot_keyboard.h
|
||||
chat_helpers/emoji_keywords.cpp
|
||||
|
|
|
@ -34,6 +34,7 @@ callShadow: Shadow {
|
|||
fallback: windowShadowFgFallback;
|
||||
}
|
||||
callPhotoSize: 180px;
|
||||
callPhotoSmallSize: 100px;
|
||||
|
||||
callOutgoingPreviewSize: size(340px, 180px);
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,27 +34,9 @@ struct CallSignalBars;
|
|||
|
||||
namespace Calls {
|
||||
|
||||
class SignalBars final : public Ui::RpWidget {
|
||||
public:
|
||||
SignalBars(
|
||||
QWidget *parent,
|
||||
not_null<Call*> call,
|
||||
const style::CallSignalBars &st,
|
||||
Fn<void()> displayedChangedCallback = nullptr);
|
||||
|
||||
bool isDisplayed() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
void changed(int count);
|
||||
|
||||
const style::CallSignalBars &_st;
|
||||
int _count = Call::kSignalBarStarting;
|
||||
Fn<void()> _displayedChangedCallback;
|
||||
|
||||
};
|
||||
class Userpic;
|
||||
class SignalBars;
|
||||
class VideoBubble;
|
||||
|
||||
class Panel final : public Ui::RpWidget, private Ui::AbstractTooltipShower {
|
||||
|
||||
|
@ -93,12 +75,7 @@ private:
|
|||
void hideDeactivated();
|
||||
void createBottomImage();
|
||||
void createDefaultCacheImage();
|
||||
void refreshCacheImageUserPhoto();
|
||||
|
||||
void processUserPhoto();
|
||||
void refreshUserPhoto();
|
||||
bool isGoodUserPhoto(PhotoData *photo);
|
||||
void createUserpicCache(Image *image);
|
||||
QRect signalBarsRect() const;
|
||||
void paintSignalBarsBg(Painter &p);
|
||||
|
||||
|
@ -115,10 +92,12 @@ private:
|
|||
void destroyDelayed();
|
||||
void setIncomingShown(bool shown);
|
||||
|
||||
[[nodiscard]] bool hasActiveVideo() const;
|
||||
void checkForInactiveHide();
|
||||
void checkForInactiveShow();
|
||||
|
||||
Call *_call = nullptr;
|
||||
not_null<UserData*> _user;
|
||||
std::shared_ptr<Data::CloudImageView> _userpic;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
|
||||
bool _useTransparency = true;
|
||||
bool _incomingShown = false;
|
||||
|
@ -142,7 +121,9 @@ private:
|
|||
object_ptr<Ui::IconButton> _mute;
|
||||
object_ptr<Ui::FlatLabel> _name;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
object_ptr<SignalBars> _signalBars;
|
||||
object_ptr<SignalBars> _signalBars = { nullptr };
|
||||
std::unique_ptr<Userpic> _userpic;
|
||||
std::unique_ptr<VideoBubble> _outgoingVideoBubble;
|
||||
std::vector<EmojiPtr> _fingerprint;
|
||||
QRect _fingerprintArea;
|
||||
|
||||
|
@ -150,9 +131,6 @@ private:
|
|||
base::Timer _updateOuterRippleTimer;
|
||||
|
||||
bool _visible = false;
|
||||
QPixmap _userPhoto;
|
||||
PhotoId _userPhotoId = 0;
|
||||
bool _userPhotoFull = false;
|
||||
|
||||
Ui::Animations::Simple _opacityAnimation;
|
||||
QPixmap _animationCache;
|
||||
|
|
77
Telegram/SourceFiles/calls/calls_signal_bars.cpp
Normal file
77
Telegram/SourceFiles/calls/calls_signal_bars.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_signal_bars.h"
|
||||
|
||||
#include "calls/calls_call.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
SignalBars::SignalBars(
|
||||
QWidget *parent,
|
||||
not_null<Call*> call,
|
||||
const style::CallSignalBars &st,
|
||||
Fn<void()> displayedChangedCallback)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _count(Call::kSignalBarStarting)
|
||||
, _displayedChangedCallback(std::move(displayedChangedCallback)) {
|
||||
resize(
|
||||
_st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1),
|
||||
_st.width * Call::kSignalBarCount);
|
||||
call->signalBarCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
changed(count);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
bool SignalBars::isDisplayed() const {
|
||||
return (_count >= 0);
|
||||
}
|
||||
|
||||
void SignalBars::paintEvent(QPaintEvent *e) {
|
||||
if (!isDisplayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Painter p(this);
|
||||
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_st.color);
|
||||
for (auto i = 0; i < Call::kSignalBarCount; ++i) {
|
||||
p.setOpacity((i < _count) ? 1. : _st.inactiveOpacity);
|
||||
const auto barHeight = (i + 1) * _st.width;
|
||||
const auto barLeft = i * (_st.width + _st.skip);
|
||||
const auto barTop = height() - barHeight;
|
||||
p.drawRoundedRect(
|
||||
barLeft,
|
||||
barTop,
|
||||
_st.width,
|
||||
barHeight,
|
||||
_st.radius,
|
||||
_st.radius);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void SignalBars::changed(int count) {
|
||||
if (_count == Call::kSignalBarFinished) {
|
||||
return;
|
||||
}
|
||||
if (_count != count) {
|
||||
const auto wasDisplayed = isDisplayed();
|
||||
_count = count;
|
||||
if (isDisplayed() != wasDisplayed && _displayedChangedCallback) {
|
||||
_displayedChangedCallback();
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Calls
|
42
Telegram/SourceFiles/calls/calls_signal_bars.h
Normal file
42
Telegram/SourceFiles/calls/calls_signal_bars.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace style {
|
||||
struct CallSignalBars;
|
||||
} // namespace style
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Call;
|
||||
|
||||
class SignalBars final : public Ui::RpWidget {
|
||||
public:
|
||||
SignalBars(
|
||||
QWidget *parent,
|
||||
not_null<Call*> call,
|
||||
const style::CallSignalBars &st,
|
||||
Fn<void()> displayedChangedCallback = nullptr);
|
||||
|
||||
bool isDisplayed() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
void changed(int count);
|
||||
|
||||
const style::CallSignalBars &_st;
|
||||
int _count = 0;
|
||||
Fn<void()> _displayedChangedCallback;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
|
@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/application.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "calls/calls_signal_bars.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "main/main_session.h"
|
||||
|
|
192
Telegram/SourceFiles/calls/calls_userpic.cpp
Normal file
192
Telegram/SourceFiles/calls/calls_userpic.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_userpic.h"
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "apiwrap.h" // requestFullPeer.
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
Userpic::Userpic(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<bool> muted)
|
||||
: _content(parent)
|
||||
, _peer(peer) {
|
||||
setGeometry(0, 0, 0);
|
||||
setup(std::move(muted));
|
||||
}
|
||||
|
||||
Userpic::~Userpic() = default;
|
||||
|
||||
void Userpic::setVisible(bool visible) {
|
||||
_content.setVisible(visible);
|
||||
}
|
||||
|
||||
void Userpic::setGeometry(int x, int y, int size) {
|
||||
if (this->size() != size) {
|
||||
_userPhoto = QPixmap();
|
||||
_userPhotoFull = false;
|
||||
refreshPhoto();
|
||||
}
|
||||
_content.setGeometry(x, y, size, size);
|
||||
_content.update();
|
||||
}
|
||||
|
||||
void Userpic::setup(rpl::producer<bool> muted) {
|
||||
_content.show();
|
||||
_content.setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_content.paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
paint();
|
||||
}, lifetime());
|
||||
|
||||
std::move(
|
||||
muted
|
||||
) | rpl::start_with_next([=](bool muted) {
|
||||
setMuted(muted);
|
||||
}, lifetime());
|
||||
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
_peer,
|
||||
Data::PeerUpdate::Flag::Photo
|
||||
) | rpl::start_with_next([=] {
|
||||
processPhoto();
|
||||
}, lifetime());
|
||||
|
||||
_peer->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshPhoto();
|
||||
}, lifetime());
|
||||
|
||||
_mutedAnimation.stop();
|
||||
}
|
||||
|
||||
void Userpic::paint() {
|
||||
Painter p(&_content);
|
||||
|
||||
p.drawPixmap(0, 0, _userPhoto);
|
||||
}
|
||||
|
||||
void Userpic::setMuted(bool muted) {
|
||||
if (_muted == muted) {
|
||||
return;
|
||||
}
|
||||
_muted = muted;
|
||||
_mutedAnimation.start(
|
||||
[=] { _content.update(); },
|
||||
_muted ? 0. : 1.,
|
||||
_muted ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}
|
||||
|
||||
int Userpic::size() const {
|
||||
return _content.width();
|
||||
}
|
||||
|
||||
void Userpic::processPhoto() {
|
||||
_userpic = _peer->createUserpicView();
|
||||
_peer->loadUserpic();
|
||||
const auto photo = _peer->userpicPhotoId()
|
||||
? _peer->owner().photo(_peer->userpicPhotoId()).get()
|
||||
: nullptr;
|
||||
if (isGoodPhoto(photo)) {
|
||||
_photo = photo->createMediaView();
|
||||
_photo->wanted(Data::PhotoSize::Thumbnail, _peer->userpicPhotoOrigin());
|
||||
} else {
|
||||
_photo = nullptr;
|
||||
if (_peer->userpicPhotoUnknown() || (photo && !photo->date)) {
|
||||
_peer->session().api().requestFullPeer(_peer);
|
||||
}
|
||||
}
|
||||
refreshPhoto();
|
||||
}
|
||||
|
||||
void Userpic::refreshPhoto() {
|
||||
if (!size()) {
|
||||
return;
|
||||
}
|
||||
const auto isNewBigPhoto = [&] {
|
||||
return _photo
|
||||
&& (_photo->image(Data::PhotoSize::Thumbnail) != nullptr)
|
||||
&& (_photo->owner()->id != _userPhotoId || !_userPhotoFull);
|
||||
}();
|
||||
if (isNewBigPhoto) {
|
||||
_userPhotoId = _photo->owner()->id;
|
||||
_userPhotoFull = true;
|
||||
createCache(_photo->image(Data::PhotoSize::Thumbnail));
|
||||
} else if (_userPhoto.isNull()) {
|
||||
createCache(_userpic ? _userpic->image() : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Userpic::createCache(Image *image) {
|
||||
auto size = this->size() * cIntRetinaFactor();
|
||||
auto options = Images::Option::Smooth | Images::Option::Circled;
|
||||
// _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None;
|
||||
if (image) {
|
||||
auto width = image->width();
|
||||
auto height = image->height();
|
||||
if (width > height) {
|
||||
width = qMax((width * size) / height, 1);
|
||||
height = size;
|
||||
} else {
|
||||
height = qMax((height * size) / width, 1);
|
||||
width = size;
|
||||
}
|
||||
_userPhoto = image->pixNoCache(
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
st::callPhotoSize,
|
||||
st::callPhotoSize);
|
||||
_userPhoto.setDevicePixelRatio(cRetinaFactor());
|
||||
} else {
|
||||
auto filled = QImage(QSize(st::callPhotoSize, st::callPhotoSize) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
filled.setDevicePixelRatio(cRetinaFactor());
|
||||
filled.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&filled);
|
||||
Ui::EmptyUserpic(
|
||||
Data::PeerUserpicColor(_peer->id),
|
||||
_peer->name
|
||||
).paint(p, 0, 0, st::callPhotoSize, st::callPhotoSize);
|
||||
}
|
||||
//Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight);
|
||||
_userPhoto = Images::PixmapFast(std::move(filled));
|
||||
}
|
||||
|
||||
_content.update();
|
||||
}
|
||||
|
||||
bool Userpic::isGoodPhoto(PhotoData *photo) const {
|
||||
if (!photo || photo->isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto badAspect = [](int a, int b) {
|
||||
return a > 10 * b;
|
||||
};
|
||||
const auto width = photo->width();
|
||||
const auto height = photo->height();
|
||||
return !badAspect(width, height) && !badAspect(height, width);
|
||||
}
|
||||
|
||||
} // namespace Calls
|
63
Telegram/SourceFiles/calls/calls_userpic.h
Normal file
63
Telegram/SourceFiles/calls/calls_userpic.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
class PeerData;
|
||||
class Image;
|
||||
|
||||
namespace Data {
|
||||
class CloudImageView;
|
||||
class PhotoMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Userpic final {
|
||||
public:
|
||||
Userpic(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<bool> muted);
|
||||
~Userpic();
|
||||
|
||||
void setVisible(bool visible);
|
||||
void setGeometry(int x, int y, int size);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _content.lifetime();
|
||||
}
|
||||
|
||||
private:
|
||||
void setup(rpl::producer<bool> muted);
|
||||
|
||||
void paint();
|
||||
void setMuted(bool muted);
|
||||
[[nodiscard]] int size() const;
|
||||
|
||||
void processPhoto();
|
||||
void refreshPhoto();
|
||||
[[nodiscard]] bool isGoodPhoto(PhotoData *photo) const;
|
||||
void createCache(Image *image);
|
||||
|
||||
Ui::RpWidget _content;
|
||||
|
||||
not_null<PeerData*> _peer;
|
||||
std::shared_ptr<Data::CloudImageView> _userpic;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
Ui::Animations::Simple _mutedAnimation;
|
||||
QPixmap _userPhoto;
|
||||
PhotoId _userPhotoId = 0;
|
||||
bool _userPhotoFull = false;
|
||||
bool _muted = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
154
Telegram/SourceFiles/calls/calls_video_bubble.cpp
Normal file
154
Telegram/SourceFiles/calls/calls_video_bubble.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_video_bubble.h"
|
||||
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
VideoBubble::VideoBubble(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<webrtc::VideoTrack*> track)
|
||||
: _content(parent)
|
||||
, _track(track)
|
||||
, _state(webrtc::VideoState::Inactive) {
|
||||
setup();
|
||||
}
|
||||
|
||||
void VideoBubble::setup() {
|
||||
_content.show();
|
||||
applyDragMode(_dragMode);
|
||||
|
||||
_content.paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
paint();
|
||||
}, lifetime());
|
||||
|
||||
_track->stateValue(
|
||||
) | rpl::start_with_next([=](webrtc::VideoState state) {
|
||||
setState(state);
|
||||
}, lifetime());
|
||||
|
||||
_track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_track->frameSize().isEmpty()) {
|
||||
_track->markFrameShown();
|
||||
} else {
|
||||
updateVisibility();
|
||||
_content.update();
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void VideoBubble::setDragMode(DragMode mode) {
|
||||
if (_dragMode != mode) {
|
||||
applyDragMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBubble::setBoundingRect(QRect rect) {
|
||||
_boundingRect = rect;
|
||||
setSizeConstraints(rect.size());
|
||||
}
|
||||
|
||||
void VideoBubble::applyDragMode(DragMode mode) {
|
||||
_dragMode = mode;
|
||||
if (_dragMode == DragMode::None) {
|
||||
_dragging = false;
|
||||
_content.setCursor(style::cur_default);
|
||||
}
|
||||
_content.setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents,
|
||||
(_dragMode == DragMode::None));
|
||||
}
|
||||
|
||||
void VideoBubble::setSizeConstraints(QSize min, QSize max) {
|
||||
Expects(!min.isEmpty());
|
||||
Expects(max.isEmpty() || min.width() <= max.width());
|
||||
Expects(max.isEmpty() || min.height() <= max.height());
|
||||
|
||||
if (max.isEmpty()) {
|
||||
max = min;
|
||||
}
|
||||
applySizeConstraints(min, max);
|
||||
}
|
||||
|
||||
void VideoBubble::applySizeConstraints(QSize min, QSize max) {
|
||||
_min = min;
|
||||
_max = max;
|
||||
}
|
||||
|
||||
void VideoBubble::paint() {
|
||||
Painter p(&_content);
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawImage(_content.rect(), _track->frame({}));
|
||||
_track->markFrameShown();
|
||||
}
|
||||
|
||||
void VideoBubble::setState(webrtc::VideoState state) {
|
||||
if (state == webrtc::VideoState::Paused) {
|
||||
using namespace Images;
|
||||
static constexpr auto kRadius = 24;
|
||||
_pausedFrame = Images::BlurLargeImage(_track->frame({}), kRadius);
|
||||
if (_pausedFrame.isNull()) {
|
||||
state = webrtc::VideoState::Inactive;
|
||||
}
|
||||
}
|
||||
_state = state;
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
void VideoBubble::updateSizeToFrame(QSize frame) {
|
||||
Expects(!frame.isEmpty());
|
||||
|
||||
if (_lastFrameSize == frame) {
|
||||
return;
|
||||
}
|
||||
_lastFrameSize = frame;
|
||||
|
||||
auto size = _size;
|
||||
if (size.isEmpty()) {
|
||||
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
|
||||
} else {
|
||||
const auto area = size.width() * size.height();
|
||||
const auto w = int(std::round(std::max(
|
||||
std::sqrt((frame.width() * area) / (frame.height() * 1.)),
|
||||
1.)));
|
||||
const auto h = area / w;
|
||||
size = QSize(w, h);
|
||||
}
|
||||
size = QSize(std::max(1, size.width()), std::max(1, size.height()));
|
||||
setInnerSize(size);
|
||||
}
|
||||
|
||||
void VideoBubble::setInnerSize(QSize size) {
|
||||
if (_size == size) {
|
||||
return;
|
||||
}
|
||||
_size = size;
|
||||
_content.setGeometry(
|
||||
_boundingRect.x() + (_boundingRect.width() - size.width()) / 2,
|
||||
_boundingRect.y() + (_boundingRect.height() - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height());
|
||||
}
|
||||
|
||||
void VideoBubble::updateVisibility() {
|
||||
const auto size = _track->frameSize();
|
||||
const auto visible = (_state != webrtc::VideoState::Inactive)
|
||||
&& !size.isEmpty();
|
||||
if (visible) {
|
||||
updateSizeToFrame(size);
|
||||
}
|
||||
_content.setVisible(visible);
|
||||
}
|
||||
|
||||
} // namespace Calls
|
58
Telegram/SourceFiles/calls/calls_video_bubble.h
Normal file
58
Telegram/SourceFiles/calls/calls_video_bubble.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace webrtc {
|
||||
class VideoTrack;
|
||||
enum class VideoState;
|
||||
} // namespace webrtc
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class VideoBubble final {
|
||||
public:
|
||||
VideoBubble(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<webrtc::VideoTrack*> track);
|
||||
|
||||
enum class DragMode {
|
||||
None,
|
||||
SnapToCorners,
|
||||
};
|
||||
void setDragMode(DragMode mode);
|
||||
void setBoundingRect(QRect rect);
|
||||
void setSizeConstraints(QSize min, QSize max = QSize());
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _content.lifetime();
|
||||
}
|
||||
|
||||
private:
|
||||
void setup();
|
||||
void paint();
|
||||
void setState(webrtc::VideoState state);
|
||||
void applyDragMode(DragMode mode);
|
||||
void applySizeConstraints(QSize min, QSize max);
|
||||
void updateSizeToFrame(QSize frame);
|
||||
void updateVisibility();
|
||||
void setInnerSize(QSize size);
|
||||
|
||||
Ui::RpWidget _content;
|
||||
const not_null<webrtc::VideoTrack*> _track;
|
||||
webrtc::VideoState _state = webrtc::VideoState();
|
||||
QImage _pausedFrame;
|
||||
QSize _min, _max, _size, _lastFrameSize;
|
||||
QRect _boundingRect;
|
||||
DragMode _dragMode = DragMode::None;
|
||||
bool _dragging = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
|
@ -1 +1 @@
|
|||
Subproject commit 4d13b96b4c4e4be2ada7e460203eea9fecde458d
|
||||
Subproject commit a8e19691c5d653310f7ac5f75d69e45b34771fa4
|
Loading…
Add table
Reference in a new issue