Compare commits

..

21 Commits

Author SHA1 Message Date
John Preston
486424af4f Beta version 2.5.2: Update cmake_helpers. 2020-12-25 18:57:21 +04:00
John Preston
f3614d6402 Beta version 2.5.2: Add in-app changelog. 2020-12-25 18:30:41 +04:00
John Preston
71151f6bf6 Beta version 2.5.2.
- Fix possible crash in video calls.
- Fix possible crash in connecting to voice chats.
- Use different audio module code on Windows in calls.
2020-12-25 16:45:18 +04:00
John Preston
2c0ef9c4e9 Improve connecting animation in voice chats. 2020-12-25 16:37:46 +04:00
John Preston
930e971881 Use more modern audio backend in calls on Windows. 2020-12-25 16:11:51 +04:00
John Preston
a576025d4f Always show invited at the end of voice chat. 2020-12-25 15:44:17 +04:00
John Preston
bcd2560e8f Reuse the code for userpics in Calls::TopBar. 2020-12-25 14:10:08 +04:00
John Preston
aede42b0b6 Update submodules. 2020-12-25 14:10:08 +04:00
Ilya Fedin
56728a066e Fix blurry tray icon with svg themes
QIcon::actualSize doesn't work as expected with svg themes, get actual pixmap and check its size instead.
2020-12-24 22:46:09 +03:00
John Preston
1951b7a8a1 Fix possible infinite recursion in video calls. 2020-12-24 14:38:46 +04:00
John Preston
0dc0f588c4 Don't offer sending .pdf-s as photos. 2020-12-24 13:52:38 +04:00
John Preston
7d22c631ca Fix voice chat members context menu. 2020-12-24 13:30:05 +04:00
John Preston
cf5cc3646a Fix multi-pin bar render after theme switch. 2020-12-24 07:59:34 +04:00
Ilya Fedin
375820d5cf glib was missed in stage-packages in snap 2020-12-24 07:48:00 +04:00
Ilya Fedin
3955543699 Remove QtSvg from snap in telegram part
Since it was needed only for lxqt-qtplugin
2020-12-24 07:48:00 +04:00
Ilya Fedin
c03da00e37 Fix getting version tag in snap action 2020-12-24 07:48:00 +04:00
Ilya Fedin
edcd462fb9 Use ibus portal in snap
It's supported since snapd 2.46 that was released in August
2020-12-24 07:48:00 +04:00
Ilya Fedin
e99558abeb Remove linux LastUserInputTime dependency since it's only in lib_base 2020-12-24 07:47:13 +04:00
Ilya Fedin
feff514a07 Update openal in snap to 1.21.0
And remove unneeded dependencies
2020-12-23 20:31:01 +04:00
John Preston
b1b25b0df9 Version 2.5.1.
- Fix crash in voice calls.
2020-12-23 15:01:31 +04:00
John Preston
dfee8238c6 Fix crash in legacy groups speaking typings handling. 2020-12-23 14:45:37 +04:00
34 changed files with 826 additions and 624 deletions

View File

@@ -51,6 +51,7 @@ jobs:
- name: Clone.
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: First set up.

View File

@@ -72,7 +72,6 @@ PRIVATE
if (LINUX)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_xcb_screensaver
desktop-app::external_xcb
desktop-app::external_glib
)

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.5.0.0" />
Version="2.5.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,5,0,0
PRODUCTVERSION 2,5,0,0
FILEVERSION 2,5,2,0
PRODUCTVERSION 2,5,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.5.0.0"
VALUE "FileVersion", "2.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.0.0"
VALUE "ProductVersion", "2.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,5,0,0
PRODUCTVERSION 2,5,0,0
FILEVERSION 2,5,2,0
PRODUCTVERSION 2,5,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.5.0.0"
VALUE "FileVersion", "2.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.0.0"
VALUE "ProductVersion", "2.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -928,6 +928,9 @@ void Updates::handleSendActionUpdate(
const auto isSpeakingInCall = (action.type()
== mtpc_speakingInGroupCallAction);
if (isSpeakingInCall) {
if (!peer->isChat() && !peer->isChannel()) {
return;
}
const auto call = peer->groupCall();
const auto now = crl::now();
if (call) {
@@ -943,8 +946,8 @@ void Updates::handleSendActionUpdate(
: (channel->flags() & MTPDchannel::Flag::f_call_active);
if (active) {
_pendingSpeakingCallMembers.emplace(
channel).first->second[userId] = now;
session().api().requestFullPeer(channel);
peer).first->second[userId] = now;
session().api().requestFullPeer(peer);
}
}
}

View File

@@ -1229,37 +1229,56 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
}
}
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
void PeerListContent::showRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed));
}
bool PeerListContent::showRowMenu(
RowIndex index,
QPoint globalPos,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
if (_contextMenu) {
_contextMenu->deleteLater();
_contextMenu->setDestroyedCallback(nullptr);
_contextMenu = nullptr;
}
setContexted(Selected());
if (e->reason() == QContextMenuEvent::Mouse) {
handleMouseMove(e->globalPos());
}
setContexted(_selected);
if (_pressButton != Qt::LeftButton) {
mousePressReleased(_pressButton);
}
if (const auto row = getRow(_contexted.index)) {
_contextMenu = _controller->rowContextMenu(this, row);
if (_contextMenu) {
_contextMenu->setDestroyedCallback(crl::guard(
this,
[this] {
setContexted(Selected());
handleMouseMove(QCursor::pos());
}));
_contextMenu->popup(e->globalPos());
e->accept();
} else {
const auto row = getRow(index);
if (!row) {
return false;
}
_contextMenu = _controller->rowContextMenu(this, row);
const auto raw = _contextMenu.get();
if (!raw) {
return false;
}
setContexted({ index, false });
raw->setDestroyedCallback(crl::guard(
this,
[=] {
setContexted(Selected());
}
} else {
setContexted(Selected());
handleMouseMove(QCursor::pos());
if (destroyed) {
destroyed(raw);
}
}));
raw->popup(globalPos);
return true;
}
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
if (e->reason() == QContextMenuEvent::Mouse) {
handleMouseMove(e->globalPos());
}
if (showRowMenu(_selected.index, e->globalPos())) {
e->accept();
}
}

View File

@@ -300,6 +300,9 @@ public:
peerListFinishSelectedRowsBunch();
}
virtual void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) = 0;
virtual int peerListSelectedRowsCount() = 0;
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
virtual void peerListRestoreState(
@@ -557,6 +560,10 @@ public:
std::unique_ptr<PeerListState> saveState() const;
void restoreState(std::unique_ptr<PeerListState> state);
void showRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
auto scrollToRequests() const {
return _scrollToRequests.events();
}
@@ -640,6 +647,11 @@ private:
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
bool showRowMenu(
RowIndex index,
QPoint globalPos,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
crl::time paintRow(Painter &p, crl::time ms, RowIndex index);
void addRowEntry(not_null<PeerListRow*> row);
@@ -823,6 +835,11 @@ public:
std::unique_ptr<PeerListState> state) override {
_content->restoreState(std::move(state));
}
void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) override {
_content->showRowMenu(row, std::move(destroyed));
}
protected:
not_null<PeerListContent*> content() const {

View File

@@ -9,6 +9,7 @@ using "ui/basic.style";
using "ui/widgets/widgets.style";
using "ui/layers/layers.style";
using "ui/chat/chat.style"; // GroupCallUserpics
using "window/window.style";
CallSignalBars {
@@ -605,9 +606,12 @@ groupCallButtonSkip: 43px;
groupCallButtonBottomSkip: 145px;
groupCallMuteBottomSkip: 160px;
groupCallTopBarUserpicSize: 28px;
groupCallTopBarUserpicShift: 8px;
groupCallTopBarUserpicStroke: 2px;
groupCallTopBarUserpics: GroupCallUserpics {
size: 28px;
shift: 8px;
stroke: 2px;
align: align(left);
}
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
width: -26px;
height: 26px;

View File

@@ -368,10 +368,10 @@ void Call::setupOutgoingVideo() {
_errors.fire({ ErrorType::NoCamera });
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
} else if (_state.current() != State::Established
&& state != started
&& !_videoCapture) {
&& (state != Webrtc::VideoState::Inactive)
&& (started == Webrtc::VideoState::Inactive)) {
_errors.fire({ ErrorType::NotStartedCall });
_videoOutgoing->setState(started);
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
} else if (state != Webrtc::VideoState::Inactive
&& _instance
&& !_instance->supportsVideo()) {

View File

@@ -243,6 +243,9 @@ private:
void prepareRows(not_null<Data::GroupCall*> real);
//void repaintByTimer();
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row);
void setupListChangeViewers(not_null<GroupCall*> call);
void subscribeToChanges(not_null<Data::GroupCall*> real);
void updateRow(
@@ -638,10 +641,7 @@ MembersController::MembersController(
}
MembersController::~MembersController() {
if (_menu) {
_menu->setDestroyedCallback(nullptr);
_menu = nullptr;
}
base::take(_menu);
}
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
@@ -750,7 +750,21 @@ void MembersController::updateRow(
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = delegate()->peerListFullRowsCount();
if (!count) {
return false;
}
const auto row = delegate()->peerListRowAt(count - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
delegate()->peerListAppendRow(std::move(row));
if (reorder) {
delegate()->peerListPartitionRows([](const PeerListRow &row) {
return static_cast<const Row&>(row).state() != kInvited;
});
}
}
delegate()->peerListRefreshRows();
}
@@ -1002,29 +1016,20 @@ auto MembersController::kickMemberRequests() const
}
void MembersController::rowClicked(not_null<PeerListRow*> row) {
if (_menu) {
_menu->setDestroyedCallback(nullptr);
_menu->deleteLater();
_menu = nullptr;
}
_menu = rowContextMenu(_menuParent, row);
if (const auto raw = _menu.get()) {
raw->setDestroyedCallback([=] {
if (_menu && _menu.get() != raw) {
return;
}
auto saved = base::take(_menu);
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
if (const auto row = findRow(peer->asUser())) {
if (row->speaking()) {
checkSpeakingRowPosition(row);
}
delegate()->peerListShowRowMenu(row, [=](not_null<Ui::PopupMenu*> menu) {
if (!_menu || _menu.get() != menu) {
return;
}
auto saved = base::take(_menu);
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
if (const auto row = findRow(peer->asUser())) {
if (row->speaking()) {
checkSpeakingRowPosition(row);
}
}
_menu = std::move(saved);
});
raw->popup(QCursor::pos());
}
}
_menu = std::move(saved);
});
}
void MembersController::rowActionClicked(
@@ -1035,6 +1040,23 @@ void MembersController::rowActionClicked(
base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
auto result = createRowContextMenu(parent, row);
if (result) {
// First clear _menu value, so that we don't check row positions yet.
base::take(_menu);
// Here unique_qptr is used like a shared pointer, where
// not the last destroyed pointer destroys the object, but the first.
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
}
return result;
}
base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
if (row->peer()->isSelf()) {
@@ -1125,7 +1147,9 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
_kickMemberRequests.fire_copy(user);
});
if (_peer->canManageGroupCall() && (!admin || mute)) {
if ((muteState != Row::State::Invited)
&& _peer->canManageGroupCall()
&& (!admin || mute)) {
result->addAction(
(mute
? tr::lng_group_call_context_mute(tr::now)

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/paint/blobs_linear.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/chat/group_call_userpics.h" // Ui::GroupCallUser.
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
#include "ui/layers/generic_box.h"
#include "ui/wrap/padding_wrap.h"
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "app.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h" // style::GroupCallUserpics
#include "styles/style_layers.h"
namespace Calls {
@@ -165,7 +167,7 @@ void DebugInfoBox::updateText() {
} // namespace
struct TopBar::User {
Ui::GroupCallBarContent::User data;
Ui::GroupCallUser data;
};
class Mute final : public Ui::IconButton {
@@ -238,6 +240,12 @@ TopBar::TopBar(
: RpWidget(parent)
, _call(call)
, _groupCall(groupCall)
, _userpics(call
? nullptr
: std::make_unique<Ui::GroupCallUserpics>(
st::groupCallTopBarUserpics,
rpl::single(true),
[=] { updateUserpics(); }))
, _durationLabel(_call
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
: object_ptr<Ui::LabelSimple>(nullptr))
@@ -551,35 +559,31 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
) | rpl::map([=](not_null<Data::GroupCall*> real) {
return HistoryView::GroupCallTracker::ContentByCall(
real,
HistoryView::UserpicsInRowStyle{
.size = st::groupCallTopBarUserpicSize,
.shift = st::groupCallTopBarUserpicShift,
.stroke = st::groupCallTopBarUserpicStroke,
});
st::groupCallTopBarUserpics.size);
}) | rpl::flatten_latest(
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
if (_users.size() != content.users.size()) {
return true;
}
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
if (_users[i].data.userpicKey != content.users[i].userpicKey
|| _users[i].data.id != content.users[i].id) {
if (_users[i].userpicKey != content.users[i].userpicKey
|| _users[i].id != content.users[i].id) {
return true;
}
}
return false;
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
const auto sizeChanged = (_users.size() != content.users.size());
_users = ranges::view::all(
content.users
) | ranges::view::transform([](const auto &user) {
return User{ user };
}) | ranges::to_vector;
generateUserpicsInRow();
if (sizeChanged) {
updateControlsGeometry();
_users = content.users;
for (auto &user : _users) {
user.speaking = false;
}
update();
_userpics->update(_users, !isHidden());
}, lifetime());
_userpics->widthValue(
) | rpl::start_with_next([=](int width) {
_userpicsWidth = width;
updateControlsGeometry();
}, lifetime());
call->peer()->session().changes().peerUpdates(
@@ -591,41 +595,10 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
}) | rpl::start_with_next([=] {
updateInfoLabels();
}, lifetime());
}
void TopBar::generateUserpicsInRow() {
const auto count = int(_users.size());
if (!count) {
_userpics = QImage();
return;
}
const auto limit = std::min(count, kMaxUsersInBar);
const auto single = st::groupCallTopBarUserpicSize;
const auto shift = st::groupCallTopBarUserpicShift;
const auto width = single + (limit - 1) * (single - shift);
if (_userpics.width() != width * cIntRetinaFactor()) {
_userpics = QImage(
QSize(width, single) * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
}
_userpics.fill(Qt::transparent);
_userpics.setDevicePixelRatio(cRetinaFactor());
auto q = Painter(&_userpics);
auto hq = PainterHighQualityEnabler(q);
auto pen = QPen(Qt::transparent);
pen.setWidth(st::groupCallTopBarUserpicStroke);
auto x = (count - 1) * (single - shift);
for (auto i = count; i != 0;) {
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
q.drawImage(x, 0, _users[--i].data.userpic);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.setBrush(Qt::NoBrush);
q.setPen(pen);
q.drawEllipse(x, 0, single, single);
x -= single - shift;
}
void TopBar::updateUserpics() {
update(_mute->width(), 0, _userpics->maxWidth(), height());
}
void TopBar::updateInfoLabels() {
@@ -688,8 +661,13 @@ void TopBar::updateControlsGeometry() {
_durationLabel->moveToLeft(left, st::callBarLabelTop);
left += _durationLabel->width() + st::callBarSkip;
}
if (!_userpics.isNull()) {
left += _userpics.width() / _userpics.devicePixelRatio();
if (_userpicsWidth) {
const auto single = st::groupCallTopBarUserpics.size;
const auto skip = anim::interpolate(
0,
st::callBarSkip,
std::min(_userpicsWidth, single) / float64(single));
left += _userpicsWidth + skip;
}
if (_signalBars) {
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
@@ -741,11 +719,10 @@ void TopBar::paintEvent(QPaintEvent *e) {
: (_muted ? st::callBarBgMuted : st::callBarBg);
p.fillRect(e->rect(), std::move(brush));
if (!_userpics.isNull()) {
const auto imageSize = _userpics.size()
/ _userpics.devicePixelRatio();
const auto top = (height() - imageSize.height()) / 2;
p.drawImage(_mute->width(), top, _userpics);
if (_userpicsWidth) {
const auto size = st::groupCallTopBarUserpics.size;
const auto top = (height() - size) / 2;
_userpics->paint(p, _mute->width(), top, size);
}
}

View File

@@ -20,6 +20,8 @@ class IconButton;
class AbstractButton;
class LabelSimple;
class FlatLabel;
struct GroupCallUser;
class GroupCallUserpics;
} // namespace Ui
namespace Main {
@@ -66,14 +68,15 @@ private:
void setMuted(bool mute);
void subscribeToMembersChanges(not_null<GroupCall*> call);
void generateUserpicsInRow();
void updateUserpics();
const base::weak_ptr<Call> _call;
const base::weak_ptr<GroupCall> _groupCall;
bool _muted = false;
std::vector<User> _users;
QImage _userpics;
std::vector<Ui::GroupCallUser> _users;
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
int _userpicsWidth = 0;
object_ptr<Ui::LabelSimple> _durationLabel;
object_ptr<SignalBars> _signalBars;
object_ptr<Ui::FlatLabel> _fullInfoLabel;

View File

@@ -75,6 +75,14 @@ std::map<int, const char*> BetaLogs() {
"- Fix freeze on secondary screen disconnect.\n"
},
{
2005002,
"- Fix possible crash in video calls.\n"
"- Fix possible crash in connecting to voice chats.\n"
"- Use different audio module code on Windows in calls.\n"
}
};
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2005000;
constexpr auto AppVersionStr = "2.5";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 2005002;
constexpr auto AppVersionStr = "2.5.2";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h"
#include "main/main_session.h"
#include "ui/chat/group_call_bar.h"
#include "ui/chat/group_call_userpics.h"
#include "ui/painter.h"
#include "calls/calls_group_call.h"
#include "calls/calls_instance.h"
@@ -24,7 +25,7 @@ namespace HistoryView {
void GenerateUserpicsInRow(
QImage &result,
const std::vector<UserpicInRow> &list,
const UserpicsInRowStyle &st,
const style::GroupCallUserpics &st,
int maxElements) {
const auto count = int(list.size());
if (!count) {
@@ -67,7 +68,7 @@ GroupCallTracker::GroupCallTracker(not_null<PeerData*> peer)
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st) {
int userpicSize) {
struct State {
std::vector<UserpicInRow> userpics;
Ui::GroupCallBarContent current;
@@ -130,7 +131,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
static const auto RegenerateUserpics = [](
not_null<State*> state,
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st,
int userpicSize,
bool force = false) {
const auto result = FillMissingUserpics(state, call) || force;
if (!result) {
@@ -141,7 +142,9 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
state->someUserpicsNotLoaded = false;
for (auto &userpic : state->userpics) {
userpic.peer->loadUserpic();
const auto pic = userpic.peer->genUserpic(userpic.view, st.size);
const auto pic = userpic.peer->genUserpic(
userpic.view,
userpicSize);
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
state->current.users.push_back({
.userpic = pic.toImage(),
@@ -161,7 +164,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<State*> state,
not_null<Data::GroupCall*> call,
not_null<UserData*> user,
const UserpicsInRowStyle &st) {
int userpicSize) {
const auto i = ranges::find(
state->userpics,
user,
@@ -170,7 +173,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
return false;
}
state->userpics.erase(i);
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
return true;
};
@@ -178,7 +181,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
not_null<State*> state,
not_null<Data::GroupCall*> call,
not_null<UserData*> user,
const UserpicsInRowStyle &st) {
int userpicSize) {
Expects(state->userpics.size() <= kLimit);
const auto &participants = call->participants();
@@ -237,7 +240,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
}
Assert(state->userpics.size() <= kLimit);
}
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
return true;
};
@@ -262,12 +265,12 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
const auto user = update.now ? update.now->user : update.was->user;
if (!update.now) {
if (RemoveUserpic(state, call, user, st)) {
if (RemoveUserpic(state, call, user, userpicSize)) {
pushNext();
}
} else if (update.now->speaking
&& (!update.was || !update.was->speaking)) {
if (CheckPushToFront(state, call, user, st)) {
if (CheckPushToFront(state, call, user, userpicSize)) {
pushNext();
}
} else {
@@ -287,7 +290,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
updateSpeakingState = false;
}
}
if (RegenerateUserpics(state, call, st)
if (RegenerateUserpics(state, call, userpicSize)
|| updateSpeakingState) {
pushNext();
}
@@ -296,7 +299,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
call->participantsSliceAdded(
) | rpl::filter([=] {
return RegenerateUserpics(state, call, st);
return RegenerateUserpics(state, call, userpicSize);
}) | rpl::start_with_next(pushNext, lifetime);
call->peer()->session().downloaderTaskFinished(
@@ -306,14 +309,14 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
RegenerateUserpics(state, call, st, true);
RegenerateUserpics(state, call, userpicSize, true);
pushNext();
return;
}
}
}, lifetime);
RegenerateUserpics(state, call, st);
RegenerateUserpics(state, call, userpicSize);
call->fullCountValue(
) | rpl::start_with_next([=](int count) {
@@ -345,12 +348,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
} else if (!call->fullCount() && !call->participantsLoaded()) {
call->reload();
}
const auto st = UserpicsInRowStyle{
.size = st::historyGroupCallUserpicSize,
.shift = st::historyGroupCallUserpicShift,
.stroke = st::historyGroupCallUserpicStroke,
};
return ContentByCall(call, st);
return ContentByCall(call, st::historyGroupCallUserpics.size);
}) | rpl::flatten_latest();
}

View File

@@ -18,6 +18,10 @@ class GroupCall;
class CloudImageView;
} // namespace Data
namespace style {
struct GroupCallUserpics;
} // namespace style
namespace HistoryView {
struct UserpicInRow {
@@ -27,16 +31,10 @@ struct UserpicInRow {
mutable InMemoryKey uniqueKey;
};
struct UserpicsInRowStyle {
int size = 0;
int shift = 0;
int stroke = 0;
};
void GenerateUserpicsInRow(
QImage &result,
const std::vector<UserpicInRow> &list,
const UserpicsInRowStyle &st,
const style::GroupCallUserpics &st,
int maxElements = 0);
class GroupCallTracker final {
@@ -48,7 +46,7 @@ public:
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
not_null<Data::GroupCall*> call,
const UserpicsInRowStyle &st);
int userpicSize);
private:
const not_null<PeerData*> _peer;

View File

@@ -795,8 +795,8 @@ void Message::paintCommentsButton(
auto &list = _comments->userpics;
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
const auto count = std::min(int(views->recentRepliers.size()), limit);
const auto single = st::historyCommentsUserpicSize;
const auto shift = st::historyCommentsUserpicOverlap;
const auto single = st::historyCommentsUserpics.size;
const auto shift = st::historyCommentsUserpics.shift;
const auto regenerate = [&] {
if (list.size() != count) {
return true;
@@ -828,12 +828,11 @@ void Message::paintCommentsButton(
while (list.size() > count) {
list.pop_back();
}
const auto st = UserpicsInRowStyle{
.size = single,
.shift = shift,
.stroke = st::historyCommentsUserpicStroke,
};
GenerateUserpicsInRow(_comments->cachedUserpics, list, st, limit);
GenerateUserpicsInRow(
_comments->cachedUserpics,
list,
st::historyCommentsUserpics,
limit);
}
p.drawImage(
left,
@@ -2135,8 +2134,8 @@ int Message::minWidthForMedia() const {
const auto views = data()->Get<HistoryMessageViews>();
if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
const auto single = st::historyCommentsUserpicSize;
const auto shift = st::historyCommentsUserpicOverlap;
const auto single = st::historyCommentsUserpics.size;
const auto shift = st::historyCommentsUserpics.shift;
const auto added = single
+ (limit - 1) * (single - shift)
+ st::historyCommentsSkipLeft

View File

@@ -209,6 +209,10 @@ QIcon TrayIconGen(int counter, bool muted) {
48,
};
static const auto dprSize = [](const QImage &image) {
return image.size() / image.devicePixelRatio();
};
for (const auto iconSize : iconSizes) {
auto &currentImageBack = TrayIconImageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
@@ -221,11 +225,17 @@ QIcon TrayIconGen(int counter, bool muted) {
systemIcon = QIcon::fromTheme(iconName);
}
if (systemIcon.actualSize(desiredSize) == desiredSize) {
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
} else {
// We can't use QIcon::actualSize here
// since it works incorrectly with svg icon themes
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
const auto firstAttemptSize = dprSize(currentImageBack);
// if current icon theme is not a svg one, Qt can return
// a pixmap that less in size even if there are a bigger one
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = systemIcon.availableSizes();
const auto biggestSize = ranges::max_element(
@@ -233,18 +243,17 @@ QIcon TrayIconGen(int counter, bool muted) {
std::less<>(),
&QSize::width);
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
if ((*biggestSize).width() > firstAttemptSize.width()) {
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
}
}
} else {
currentImageBack = Core::App().logo();
}
const auto currentImageBackSize = currentImageBack.size()
/ currentImageBack.devicePixelRatio();
if (currentImageBackSize != desiredSize) {
if (dprSize(currentImageBack) != desiredSize) {
currentImageBack = currentImageBack.scaled(
desiredSize * currentImageBack.devicePixelRatio(),
Qt::IgnoreAspectRatio,
@@ -331,17 +340,33 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
static const auto templateName = AppRuntimeDirectory()
+ kTrayIconFilename.utf16();
static const auto dprSize = [](const QPixmap &pixmap) {
return pixmap.size() / pixmap.devicePixelRatio();
};
static const auto desiredSize = QSize(22, 22);
static const auto scalePixmap = [=](const QPixmap &pixmap) {
if (dprSize(pixmap) != desiredSize) {
return pixmap.scaled(
desiredSize * pixmap.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
} else {
return pixmap;
}
};
auto ret = std::make_unique<QTemporaryFile>(
templateName,
parent);
ret->open();
if (icon.actualSize(desiredSize) == desiredSize) {
icon.pixmap(desiredSize).save(ret.get());
} else {
const auto firstAttempt = icon.pixmap(desiredSize);
const auto firstAttemptSize = dprSize(firstAttempt);
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = icon.availableSizes();
const auto biggestSize = ranges::max_element(
@@ -349,14 +374,13 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
std::less<>(),
&QSize::width);
const auto iconPixmap = icon.pixmap(*biggestSize);
iconPixmap
.scaled(
desiredSize * iconPixmap.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation)
.save(ret.get());
if ((*biggestSize).width() > firstAttemptSize.width()) {
scalePixmap(icon.pixmap(*biggestSize)).save(ret.get());
} else {
scalePixmap(firstAttempt).save(ret.get());
}
} else {
scalePixmap(firstAttempt).save(ret.get());
}
ret->close();

View File

@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include <xcb/xcb.h>
#include <xcb/screensaver.h>
#include <glib.h>

View File

@@ -38,7 +38,9 @@ bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
bool ValidPhotoForAlbum(
const PreparedFileInformation::Image &image,
const QString &mime) {
if (image.animated || Core::IsMimeSticker(mime)) {
if (image.animated
|| Core::IsMimeSticker(mime)
|| (mime == u"application/pdf"_q)) {
return false;
}
const auto width = image.data.width();

View File

@@ -18,6 +18,13 @@ MessageBar {
duration: int;
}
GroupCallUserpics {
size: pixels;
shift: pixels;
stroke: pixels;
align: align;
}
defaultMessageBar: MessageBar {
title: semiboldTextStyle;
titleFg: windowActiveTextFg;
@@ -739,10 +746,13 @@ historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSel
historyCommentsButtonHeight: 40px;
historyCommentsSkipLeft: 9px;
historyCommentsSkipText: 10px;
historyCommentsUserpicSize: 25px;
historyCommentsUserpicStroke: 2px;
historyCommentsUserpicOverlap: 6px;
historyCommentsSkipRight: 8px;
historyCommentsUserpics: GroupCallUserpics {
size: 25px;
shift: 6px;
stroke: 2px;
align: align(left);
}
boxAttachEmoji: IconButton(historyAttachEmoji) {
width: 30px;
@@ -798,9 +808,12 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
historyGroupCallUserpicSize: 32px;
historyGroupCallUserpicShift: 12px;
historyGroupCallUserpicStroke: 4px;
historyGroupCallUserpics: GroupCallUserpics {
size: 32px;
shift: 12px;
stroke: 4px;
align: align(top);
}
historyGroupCallBlobMinRadius: 23px;
historyGroupCallBlobMaxRadius: 25px;

View File

@@ -7,12 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/chat/group_call_bar.h"
#include "ui/chat/message_bar.h"
#include "ui/chat/group_call_userpics.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
#include "ui/paint/blobs.h"
#include "lang/lang_keys.h"
#include "base/openssl_help.h"
#include "styles/style_chat.h"
#include "styles/style_calls.h"
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
@@ -21,71 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QtEvents>
namespace Ui {
namespace {
constexpr auto kDuration = 160;
constexpr auto kMaxUserpics = 4;
constexpr auto kWideScale = 5;
constexpr auto kBlobsEnterDuration = crl::time(250);
constexpr auto kLevelDuration = 100. + 500. * 0.23;
constexpr auto kBlobScale = 0.605;
constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kSendRandomLevelInterval = crl::time(100);
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::historyGroupCallBlobMinRadius,
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
} // namespace
struct GroupCallBar::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel)
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
}
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0;
float64 enter = 0.;
};
struct GroupCallBar::Userpic {
User data;
std::pair<uint64, uint64> cacheKey;
crl::time speakingStarted = 0;
QImage cache;
Animations::Simple leftAnimation;
Animations::Simple shownAnimation;
std::unique_ptr<BlobsAnimation> blobsAnimation;
int left = 0;
bool positionInited = false;
bool topMost = false;
bool hiding = false;
bool cacheMasked = false;
};
GroupCallBar::GroupCallBar(
not_null<QWidget*> parent,
@@ -98,16 +31,13 @@ GroupCallBar::GroupCallBar(
tr::lng_group_call_join(),
st::groupCallTopBarJoin))
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
, _randomSpeakingTimer([=] { sendRandomLevels(); }) {
, _userpics(std::make_unique<GroupCallUserpics>(
st::historyGroupCallUserpics,
std::move(hideBlobs),
[=] { updateUserpics(); })) {
_wrap.hide(anim::type::instant);
_shadow->hide();
const auto limit = kMaxUserpics;
const auto single = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
// + 1 * single for the blobs.
_maxUserpicsWidth = 2 * single + (limit - 1) * (single - shift);
_wrap.entity()->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
@@ -122,7 +52,7 @@ GroupCallBar::GroupCallBar(
copy
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
_content = content;
updateUserpicsFromContent();
_userpics->update(_content.users, !_wrap.isHidden());
_inner->update();
}, lifetime());
@@ -140,53 +70,10 @@ GroupCallBar::GroupCallBar(
_wrap.toggle(false, anim::type::normal);
}, lifetime());
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &userpic : _userpics) {
userpic.cache = QImage();
}
}, lifetime());
_speakingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop();
}
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.updateLevel(now - blobs->lastTime);
blobs->lastTime = now;
}
}
updateUserpics();
});
rpl::combine(
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
std::move(hideBlobs)
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = animDisabled || deactivated;
if (!(hide && _speakingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
}
_skipLevelUpdate = hide;
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.setLevel(0.);
}
}
if (!hide && !_speakingAnimation.animating()) {
_speakingAnimation.start();
}
_skipLevelUpdate = hide;
}, lifetime());
setupInner();
}
GroupCallBar::~GroupCallBar() {
}
GroupCallBar::~GroupCallBar() = default;
void GroupCallBar::setupInner() {
_inner->resize(0, st::historyReplyHeight);
@@ -252,142 +139,11 @@ void GroupCallBar::paint(Painter &p) {
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
: tr::lng_group_call_no_members(tr::now)));
const auto size = st::historyGroupCallUserpics.size;
// Skip shadow of the bar above.
paintUserpics(p);
}
void GroupCallBar::paintUserpics(Painter &p) {
const auto top = (st::historyReplyHeight
- st::lineWidth
- st::historyGroupCallUserpicSize) / 2 + st::lineWidth;
const auto middle = _inner->width() / 2;
const auto size = st::historyGroupCallUserpicSize;
const auto factor = style::DevicePixelRatio();
const auto &minScale = kUserpicMinScale;
for (auto &userpic : ranges::view::reverse(_userpics)) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown == 0.) {
continue;
}
validateUserpicCache(userpic);
p.setOpacity(shown);
const auto left = middle + userpic.leftAnimation.value(userpic.left);
const auto blobs = userpic.blobsAnimation.get();
const auto shownScale = 0.5 + shown / 2.;
const auto scale = shownScale * (!blobs
? 1.
: (minScale
+ (1. - minScale) * (_speakingAnimationHideLastTime
? (1. - blobs->blobs.currentLevel())
: blobs->blobs.currentLevel())));
if (blobs) {
auto hq = PainterHighQualityEnabler(p);
const auto shift = QPointF(left + size / 2., top + size / 2.);
p.translate(shift);
blobs->blobs.paint(p, st::windowActiveTextFg);
p.translate(-shift);
p.setOpacity(1.);
}
if (std::abs(scale - 1.) < 0.001) {
const auto skip = ((kWideScale - 1) / 2) * size * factor;
p.drawImage(
QRect(left, top, size, size),
userpic.cache,
QRect(skip, skip, size * factor, size * factor));
} else {
auto hq = PainterHighQualityEnabler(p);
auto target = QRect(
left + (1 - kWideScale) / 2 * size,
top + (1 - kWideScale) / 2 * size,
kWideScale * size,
kWideScale * size);
auto shrink = anim::interpolate(
(1 - kWideScale) / 2 * size,
0,
scale);
auto margins = QMargins(shrink, shrink, shrink, shrink);
p.drawImage(target.marginsAdded(margins), userpic.cache);
}
}
p.setOpacity(1.);
const auto hidden = [](const Userpic &userpic) {
return userpic.hiding && !userpic.shownAnimation.animating();
};
_userpics.erase(ranges::remove_if(_userpics, hidden), end(_userpics));
}
bool GroupCallBar::needUserpicCacheRefresh(Userpic &userpic) {
if (userpic.cache.isNull()) {
return true;
} else if (userpic.hiding) {
return false;
} else if (userpic.cacheKey != userpic.data.userpicKey) {
return true;
}
const auto shouldBeMasked = !userpic.topMost;
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
return true;
}
return !userpic.leftAnimation.animating();
}
void GroupCallBar::ensureBlobsAnimation(Userpic &userpic) {
if (userpic.blobsAnimation) {
return;
}
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
Blobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
userpic.blobsAnimation->lastTime = crl::now();
}
void GroupCallBar::sendRandomLevels() {
if (_skipLevelUpdate) {
return;
}
for (auto &userpic : _userpics) {
if (const auto blobs = userpic.blobsAnimation.get()) {
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
}
}
}
void GroupCallBar::validateUserpicCache(Userpic &userpic) {
if (!needUserpicCacheRefresh(userpic)) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
const auto full = QSize(size, size) * kWideScale * factor;
if (userpic.cache.isNull()) {
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
userpic.cache.setDevicePixelRatio(factor);
}
userpic.cacheKey = userpic.data.userpicKey;
userpic.cacheMasked = !userpic.topMost;
userpic.cache.fill(Qt::transparent);
{
Painter p(&userpic.cache);
const auto skip = (kWideScale - 1) / 2 * size;
p.drawImage(skip, skip, userpic.data.userpic);
if (userpic.cacheMasked) {
auto hq = PainterHighQualityEnabler(p);
auto pen = QPen(Qt::transparent);
pen.setWidth(st::historyGroupCallUserpicStroke);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::transparent);
p.setPen(pen);
p.drawEllipse(skip - size + shift, skip, size, size);
}
}
const auto top = (st::historyReplyHeight - st::lineWidth - size) / 2
+ st::lineWidth;
_userpics->paint(p, _inner->width() / 2, top, size);
}
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
@@ -413,127 +169,14 @@ void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) {
: regular);
}
void GroupCallBar::updateUserpicsFromContent() {
const auto idFromUserpic = [](const Userpic &userpic) {
return userpic.data.id;
};
// Use "topMost" as "willBeHidden" flag.
for (auto &userpic : _userpics) {
userpic.topMost = true;
}
for (const auto &user : _content.users) {
const auto i = ranges::find(_userpics, user.id, idFromUserpic);
if (i == end(_userpics)) {
_userpics.push_back(Userpic{ user });
toggleUserpic(_userpics.back(), true);
continue;
}
i->topMost = false;
if (i->hiding) {
toggleUserpic(*i, true);
}
i->data = user;
// Put this one after the last we are not hiding.
for (auto j = end(_userpics) - 1; j != i; --j) {
if (!j->topMost) {
ranges::rotate(i, i + 1, j + 1);
break;
}
}
}
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
// Set correct real values of "topMost" flag.
const auto userpicsBegin = begin(_userpics);
const auto userpicsEnd = end(_userpics);
auto markedTopMost = userpicsEnd;
auto hasBlobs = false;
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
auto &userpic = *i;
if (userpic.data.speaking) {
ensureBlobsAnimation(userpic);
hasBlobs = true;
} else {
userpic.blobsAnimation = nullptr;
}
if (userpic.topMost) {
toggleUserpic(userpic, false);
userpic.topMost = false;
} else if (markedTopMost == userpicsEnd) {
userpic.topMost = true;
markedTopMost = i;
}
}
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
// Bring the topMost userpic to the very beginning, above all hiding.
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
}
updateUserpicsPositions();
if (!hasBlobs) {
_randomSpeakingTimer.cancel();
_speakingAnimation.stop();
} else if (!_randomSpeakingTimer.isActive()) {
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
_speakingAnimation.start();
}
if (_wrap.isHidden()) {
for (auto &userpic : _userpics) {
userpic.shownAnimation.stop();
userpic.leftAnimation.stop();
}
}
}
void GroupCallBar::toggleUserpic(Userpic &userpic, bool shown) {
userpic.hiding = !shown;
userpic.shownAnimation.start(
[=] { updateUserpics(); },
shown ? 0. : 1.,
shown ? 1. : 0.,
kDuration);
}
void GroupCallBar::updateUserpicsPositions() {
const auto shownCount = ranges::count(_userpics, false, &Userpic::hiding);
if (!shownCount) {
return;
}
const auto single = st::historyGroupCallUserpicSize;
const auto shift = st::historyGroupCallUserpicShift;
// + 1 * single for the blobs.
const auto fullWidth = single + (shownCount - 1) * (single - shift);
auto left = (-fullWidth / 2);
for (auto &userpic : _userpics) {
if (userpic.hiding) {
continue;
}
if (!userpic.positionInited) {
userpic.positionInited = true;
userpic.left = left;
} else if (userpic.left != left) {
userpic.leftAnimation.start(
[=] { updateUserpics(); },
userpic.left,
left,
kDuration);
userpic.left = left;
}
left += (single - shift);
}
}
void GroupCallBar::updateUserpics() {
const auto widget = _wrap.entity();
const auto middle = widget->width() / 2;
_wrap.entity()->update(
(middle - _maxUserpicsWidth / 2),
const auto width = _userpics->maxWidth();
widget->update(
(middle - width / 2),
0,
_maxUserpicsWidth,
width,
widget->height());
}

View File

@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/animations.h"
#include "base/object_ptr.h"
#include "base/timer.h"
class Painter;
@@ -18,17 +17,13 @@ namespace Ui {
class PlainShadow;
class RoundButton;
struct GroupCallUser;
class GroupCallUserpics;
struct GroupCallBarContent {
struct User {
QImage userpic;
std::pair<uint64, uint64> userpicKey = {};
int32 id = 0;
bool speaking = false;
};
int count = 0;
bool shown = false;
std::vector<User> users;
std::vector<GroupCallUser> users;
};
class GroupCallBar final {
@@ -58,24 +53,13 @@ public:
}
private:
using User = GroupCallBarContent::User;
struct BlobsAnimation;
struct Userpic;
using User = GroupCallUser;
void updateShadowGeometry(QRect wrapGeometry);
void updateControlsGeometry(QRect wrapGeometry);
void updateUserpicsFromContent();
void updateUserpics();
void setupInner();
void paint(Painter &p);
void paintUserpics(Painter &p);
void toggleUserpic(Userpic &userpic, bool shown);
void updateUserpics();
void updateUserpicsPositions();
void validateUserpicCache(Userpic &userpic);
[[nodiscard]] bool needUserpicCacheRefresh(Userpic &userpic);
void ensureBlobsAnimation(Userpic &userpic);
void sendRandomLevels();
SlideWrap<> _wrap;
not_null<RpWidget*> _inner;
@@ -83,17 +67,11 @@ private:
std::unique_ptr<PlainShadow> _shadow;
rpl::event_stream<> _barClicks;
Fn<QRect(QRect)> _shadowGeometryPostprocess;
std::vector<Userpic> _userpics;
base::Timer _randomSpeakingTimer;
Ui::Animations::Basic _speakingAnimation;
int _maxUserpicsWidth = 0;
bool _shouldBeShown = false;
bool _forceHidden = false;
bool _skipLevelUpdate = false;
crl::time _speakingAnimationHideLastTime = 0;
GroupCallBarContent _content;
std::unique_ptr<GroupCallUserpics> _userpics;
};

View File

@@ -0,0 +1,416 @@
/*
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 "ui/chat/group_call_userpics.h"
#include "ui/paint/blobs.h"
#include "base/openssl_help.h"
#include "styles/style_chat.h"
namespace Ui {
namespace {
constexpr auto kDuration = 160;
constexpr auto kMaxUserpics = 4;
constexpr auto kWideScale = 5;
constexpr auto kBlobsEnterDuration = crl::time(250);
constexpr auto kLevelDuration = 100. + 500. * 0.23;
constexpr auto kBlobScale = 0.605;
constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kSendRandomLevelInterval = crl::time(100);
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::historyGroupCallBlobMinRadius,
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
} // namespace
struct GroupCallUserpics::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel)
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
}
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSpeakingUpdateTime = 0;
float64 enter = 0.;
};
struct GroupCallUserpics::Userpic {
User data;
std::pair<uint64, uint64> cacheKey;
crl::time speakingStarted = 0;
QImage cache;
Animations::Simple leftAnimation;
Animations::Simple shownAnimation;
std::unique_ptr<BlobsAnimation> blobsAnimation;
int left = 0;
bool positionInited = false;
bool topMost = false;
bool hiding = false;
bool cacheMasked = false;
};
GroupCallUserpics::GroupCallUserpics(
const style::GroupCallUserpics &st,
rpl::producer<bool> &&hideBlobs,
Fn<void()> repaint)
: _st(st)
, _randomSpeakingTimer([=] { sendRandomLevels(); })
, _repaint(std::move(repaint)) {
const auto limit = kMaxUserpics;
const auto single = _st.size;
const auto shift = _st.shift;
// + 1 * single for the blobs.
_maxWidth = 2 * single + (limit - 1) * (single - shift);
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &userpic : _list) {
userpic.cache = QImage();
}
}, lifetime());
_speakingAnimation.init([=](crl::time now) {
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
&& (now - last >= kBlobsEnterDuration)) {
_speakingAnimation.stop();
}
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.updateLevel(now - blobs->lastTime);
blobs->lastTime = now;
}
}
if (const auto onstack = _repaint) {
onstack();
}
});
rpl::combine(
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
std::move(hideBlobs)
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
const auto hide = animDisabled || deactivated;
if (!(hide && _speakingAnimationHideLastTime)) {
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
}
_skipLevelUpdate = hide;
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
blobs->blobs.setLevel(0.);
}
}
if (!hide && !_speakingAnimation.animating()) {
_speakingAnimation.start();
}
_skipLevelUpdate = hide;
}, lifetime());
}
GroupCallUserpics::~GroupCallUserpics() = default;
void GroupCallUserpics::paint(Painter &p, int x, int y, int size) {
const auto factor = style::DevicePixelRatio();
const auto &minScale = kUserpicMinScale;
for (auto &userpic : ranges::view::reverse(_list)) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown == 0.) {
continue;
}
validateCache(userpic);
p.setOpacity(shown);
const auto left = x + userpic.leftAnimation.value(userpic.left);
const auto blobs = userpic.blobsAnimation.get();
const auto shownScale = 0.5 + shown / 2.;
const auto scale = shownScale * (!blobs
? 1.
: (minScale
+ (1. - minScale) * (_speakingAnimationHideLastTime
? (1. - blobs->blobs.currentLevel())
: blobs->blobs.currentLevel())));
if (blobs) {
auto hq = PainterHighQualityEnabler(p);
const auto shift = QPointF(left + size / 2., y + size / 2.);
p.translate(shift);
blobs->blobs.paint(p, st::windowActiveTextFg);
p.translate(-shift);
p.setOpacity(1.);
}
if (std::abs(scale - 1.) < 0.001) {
const auto skip = ((kWideScale - 1) / 2) * size * factor;
p.drawImage(
QRect(left, y, size, size),
userpic.cache,
QRect(skip, skip, size * factor, size * factor));
} else {
auto hq = PainterHighQualityEnabler(p);
auto target = QRect(
left + (1 - kWideScale) / 2 * size,
y + (1 - kWideScale) / 2 * size,
kWideScale * size,
kWideScale * size);
auto shrink = anim::interpolate(
(1 - kWideScale) / 2 * size,
0,
scale);
auto margins = QMargins(shrink, shrink, shrink, shrink);
p.drawImage(target.marginsAdded(margins), userpic.cache);
}
}
p.setOpacity(1.);
const auto hidden = [](const Userpic &userpic) {
return userpic.hiding && !userpic.shownAnimation.animating();
};
_list.erase(ranges::remove_if(_list, hidden), end(_list));
}
int GroupCallUserpics::maxWidth() const {
return _maxWidth;
}
rpl::producer<int> GroupCallUserpics::widthValue() const {
return _width.value();
}
bool GroupCallUserpics::needCacheRefresh(Userpic &userpic) {
if (userpic.cache.isNull()) {
return true;
} else if (userpic.hiding) {
return false;
} else if (userpic.cacheKey != userpic.data.userpicKey) {
return true;
}
const auto shouldBeMasked = !userpic.topMost;
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
return true;
}
return !userpic.leftAnimation.animating();
}
void GroupCallUserpics::ensureBlobsAnimation(Userpic &userpic) {
if (userpic.blobsAnimation) {
return;
}
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
Blobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
userpic.blobsAnimation->lastTime = crl::now();
}
void GroupCallUserpics::sendRandomLevels() {
if (_skipLevelUpdate) {
return;
}
for (auto &userpic : _list) {
if (const auto blobs = userpic.blobsAnimation.get()) {
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
}
}
}
void GroupCallUserpics::validateCache(Userpic &userpic) {
if (!needCacheRefresh(userpic)) {
return;
}
const auto factor = style::DevicePixelRatio();
const auto size = _st.size;
const auto shift = _st.shift;
const auto full = QSize(size, size) * kWideScale * factor;
if (userpic.cache.isNull()) {
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
userpic.cache.setDevicePixelRatio(factor);
}
userpic.cacheKey = userpic.data.userpicKey;
userpic.cacheMasked = !userpic.topMost;
userpic.cache.fill(Qt::transparent);
{
Painter p(&userpic.cache);
const auto skip = (kWideScale - 1) / 2 * size;
p.drawImage(skip, skip, userpic.data.userpic);
if (userpic.cacheMasked) {
auto hq = PainterHighQualityEnabler(p);
auto pen = QPen(Qt::transparent);
pen.setWidth(_st.stroke);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::transparent);
p.setPen(pen);
p.drawEllipse(skip - size + shift, skip, size, size);
}
}
}
void GroupCallUserpics::update(
const std::vector<GroupCallUser> &users,
bool visible) {
const auto idFromUserpic = [](const Userpic &userpic) {
return userpic.data.id;
};
// Use "topMost" as "willBeHidden" flag.
for (auto &userpic : _list) {
userpic.topMost = true;
}
for (const auto &user : users) {
const auto i = ranges::find(_list, user.id, idFromUserpic);
if (i == end(_list)) {
_list.push_back(Userpic{ user });
toggle(_list.back(), true);
continue;
}
i->topMost = false;
if (i->hiding) {
toggle(*i, true);
}
i->data = user;
// Put this one after the last we are not hiding.
for (auto j = end(_list) - 1; j != i; --j) {
if (!j->topMost) {
ranges::rotate(i, i + 1, j + 1);
break;
}
}
}
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
// Set correct real values of "topMost" flag.
const auto userpicsBegin = begin(_list);
const auto userpicsEnd = end(_list);
auto markedTopMost = userpicsEnd;
auto hasBlobs = false;
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
auto &userpic = *i;
if (userpic.data.speaking) {
ensureBlobsAnimation(userpic);
hasBlobs = true;
} else {
userpic.blobsAnimation = nullptr;
}
if (userpic.topMost) {
toggle(userpic, false);
userpic.topMost = false;
} else if (markedTopMost == userpicsEnd) {
userpic.topMost = true;
markedTopMost = i;
}
}
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
// Bring the topMost userpic to the very beginning, above all hiding.
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
}
updatePositions();
if (!hasBlobs) {
_randomSpeakingTimer.cancel();
_speakingAnimation.stop();
} else if (!_randomSpeakingTimer.isActive()) {
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
_speakingAnimation.start();
}
if (!visible) {
for (auto &userpic : _list) {
userpic.shownAnimation.stop();
userpic.leftAnimation.stop();
}
}
recountAndRepaint();
}
void GroupCallUserpics::toggle(Userpic &userpic, bool shown) {
userpic.hiding = !shown;
userpic.shownAnimation.start(
[=] { recountAndRepaint(); },
shown ? 0. : 1.,
shown ? 1. : 0.,
kDuration);
}
void GroupCallUserpics::updatePositions() {
const auto shownCount = ranges::count(_list, false, &Userpic::hiding);
if (!shownCount) {
return;
}
const auto single = _st.size;
const auto shift = _st.shift;
// + 1 * single for the blobs.
const auto fullWidth = single + (shownCount - 1) * (single - shift);
auto left = (_st.align & Qt::AlignLeft)
? 0
: (_st.align & Qt::AlignHCenter)
? (-fullWidth / 2)
: -fullWidth;
for (auto &userpic : _list) {
if (userpic.hiding) {
continue;
}
if (!userpic.positionInited) {
userpic.positionInited = true;
userpic.left = left;
} else if (userpic.left != left) {
userpic.leftAnimation.start(
_repaint,
userpic.left,
left,
kDuration);
userpic.left = left;
}
left += (single - shift);
}
}
void GroupCallUserpics::recountAndRepaint() {
auto width = 0;
auto maxShown = 0.;
for (const auto &userpic : _list) {
const auto shown = userpic.shownAnimation.value(
userpic.hiding ? 0. : 1.);
if (shown > maxShown) {
maxShown = shown;
}
width += anim::interpolate(0, _st.size - _st.shift, shown);
}
_width = width + anim::interpolate(0, _st.shift, maxShown);
if (_repaint) {
_repaint();
}
}
} // namespace Ui

View File

@@ -0,0 +1,73 @@
/*
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 "base/timer.h"
namespace style {
struct GroupCallUserpics;
} // namespace style
namespace Ui {
struct GroupCallUser {
QImage userpic;
std::pair<uint64, uint64> userpicKey = {};
int32 id = 0;
bool speaking = false;
};
class GroupCallUserpics final {
public:
GroupCallUserpics(
const style::GroupCallUserpics &st,
rpl::producer<bool> &&hideBlobs,
Fn<void()> repaint);
~GroupCallUserpics();
void update(
const std::vector<GroupCallUser> &users,
bool visible);
void paint(Painter &p, int x, int y, int size);
[[nodiscard]] int maxWidth() const;
[[nodiscard]] rpl::producer<int> widthValue() const;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
using User = GroupCallUser;
struct BlobsAnimation;
struct Userpic;
void toggle(Userpic &userpic, bool shown);
void updatePositions();
void validateCache(Userpic &userpic);
[[nodiscard]] bool needCacheRefresh(Userpic &userpic);
void ensureBlobsAnimation(Userpic &userpic);
void sendRandomLevels();
void recountAndRepaint();
const style::GroupCallUserpics &_st;
std::vector<Userpic> _list;
base::Timer _randomSpeakingTimer;
Fn<void()> _repaint;
Ui::Animations::Basic _speakingAnimation;
int _maxWidth = 0;
bool _skipLevelUpdate = false;
crl::time _speakingAnimationHideLastTime = 0;
rpl::variable<int> _width;
rpl::lifetime _lifetime;
};
} // namespace Ui

View File

@@ -45,6 +45,11 @@ MessageBar::MessageBar(not_null<QWidget*> parent, const style::MessageBar &st)
: _st(st)
, _widget(parent) {
setup();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topBarGradient = _bottomBarGradient = QPixmap();
}, _widget.lifetime());
}
void MessageBar::setup() {

View File

@@ -1,7 +1,7 @@
AppVersion 2005000
AppVersion 2005002
AppVersionStrMajor 2.5
AppVersionStrSmall 2.5
AppVersionStr 2.5.0
BetaChannel 0
AppVersionStrSmall 2.5.2
AppVersionStr 2.5.2
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 2.5
AppVersionOriginal 2.5.2.beta

View File

@@ -80,6 +80,8 @@ PRIVATE
ui/chat/attach/attach_single_media_preview.h
ui/chat/group_call_bar.cpp
ui/chat/group_call_bar.h
ui/chat/group_call_userpics.cpp
ui/chat/group_call_userpics.h
ui/chat/message_bar.cpp
ui/chat/message_bar.h
ui/chat/pinned_bar.cpp

View File

@@ -1,3 +1,13 @@
2.5.2 beta (25.12.20)
- Fix possible crash in video calls.
- Fix possible crash in connecting to voice chats.
- Use different audio module code on Windows in calls.
2.5.1 (23.12.20)
- Fix crash in voice calls.
2.5 (23.12.20)
- Turn any of your group chats into a hop-on, hop-off conference call.

2
cmake

Submodule cmake updated: a81345a28d...2208358765

View File

@@ -20,6 +20,8 @@ apps:
environment:
# Tell glib to use portals on file associations handling.
GTK_USE_PORTAL: 1
# Use sandboxed ibus api
IBUS_USE_PORTAL: 1
plugs:
- alsa
- audio-playback
@@ -75,7 +77,6 @@ parts:
- liblzma-dev
- libopus-dev
- libpulse-dev
- libqt5svg5-dev
- libqt5waylandclient5-dev
- libssl-dev
- libxcb1-dev
@@ -87,11 +88,11 @@ parts:
- qt5-image-formats-plugins
- qtwayland5
- libasound2
- libglib2.0-0
- libgtk-3-0
- liblzma5
- libopus0
- libpulse0
- libqt5svg5
- libqt5waylandclient5
- libssl1.1
- libxcb1
@@ -174,16 +175,6 @@ parts:
after:
- mozjpeg
# Qt checks that ibus-daemon binary is present, otherwise doesn't work
ibus:
plugin: nil
stage-packages:
- ibus
stage:
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2
after:
- mozjpeg
ffmpeg:
plugin: nil
build-packages:
@@ -223,18 +214,14 @@ parts:
openal:
source: https://github.com/kcat/openal-soft.git
source-depth: 1
source-tag: openal-soft-1.20.1
source-tag: openal-soft-1.21.0
plugin: cmake
build-packages:
- libasound2-dev
- libpulse-dev
- libsndio-dev
- portaudio19-dev
stage-packages:
- libasound2
- libpulse0
- libportaudio2
- libsndio7.0
cmake-parameters:
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr