Compare commits

..

43 Commits

Author SHA1 Message Date
John Preston
373635a765 Beta version 2.5.3.
- Allow using mouse buttons in Push-to-Talk shortcut.
- Fix blurred thumbnails in Shared Links section.
2020-12-30 17:53:37 +04:00
John Preston
0ecd4d3b40 Close PiP when opening a loading video.
Fixes #9926.
2020-12-30 17:12:40 +04:00
John Preston
3fd62d51aa Hide bot command start button when editing message.
Fixes #9941.
2020-12-30 16:27:32 +04:00
John Preston
818624e051 Don't allow kicking yourself from legacy group. 2020-12-30 16:14:13 +04:00
John Preston
a6eb241ec1 Fix blurred thumbnails in Shared Links section. 2020-12-30 16:13:07 +04:00
John Preston
1d7fb6c4ce Better count voice chat participants count. 2020-12-30 15:53:01 +04:00
John Preston
2af63ec48f Correctly show legacy groups with no admins. 2020-12-30 13:28:35 +04:00
John Preston
0709bc6d70 Fix links in voice chat admin log events. 2020-12-30 13:16:00 +04:00
John Preston
3a34881488 Highlight album part that had a reply clicked. 2020-12-30 12:56:44 +04:00
Ilya Fedin
39f9147790 Check for dbus menu exporter instead of menu path 2020-12-30 11:50:01 +04:00
Ilya Fedin
19a5dcbffc Make OpenAL debugging easier 2020-12-30 11:49:30 +04:00
John Preston
e864aa2ff2 Create audio device module uniformely. 2020-12-30 11:02:18 +04:00
23rd
fe85a8256a Added Github Action that updates copyright year. 2020-12-30 10:59:49 +04:00
23rd
f24b0c6237 Fixed hiding cancel button in state of listen to recorded voice data. 2020-12-30 10:59:49 +04:00
23rd
3940d57c3d Disabled message editing while voice recording. 2020-12-30 10:59:49 +04:00
Ilya Fedin
8da33113a2 Use DeviceModelPretty/SystemVersionPretty directly
This allows using methods that require a running QGuiApplication instance to detect system
2020-12-29 12:36:47 +04:00
Ilya Fedin
f66cfb5684 Use new IsSupportedByWM XCB API from lib_base 2020-12-29 12:29:11 +04:00
John Preston
d648d294ca Fix layout in intro Settings. 2020-12-28 18:29:09 +04:00
John Preston
0c8033414e Allow using mouse buttons in global shortcuts. 2020-12-28 17:06:08 +04:00
Stepan Skryabin
28f29b51c0 Update supported operating systems 2020-12-28 17:01:27 +04:00
Ilya Fedin
8142e83395 Fix connection to QSystemTrayIcon::messageClicked in main_window_win 2020-12-28 17:00:05 +04:00
Ilya Fedin
e247be7e33 Operate with QString instead of QDBusObjectPath 2020-12-28 17:00:05 +04:00
Ilya Fedin
e594b75f4c Use more forward declarations in main_window_linux 2020-12-28 17:00:05 +04:00
Ilya Fedin
28f857f763 Add support for G-S-D's media-keys extension
This fixes media keys handling on (but not limited to, probably):
* GNOME
* Cinnamon
* MATE
* Budgie
* Pantheon (elementaryOS)
* Unity
2020-12-28 17:00:05 +04:00
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
76 changed files with 1412 additions and 967 deletions

View File

@@ -0,0 +1,16 @@
name: Copyright year updater.
on:
repository_dispatch:
types: ["Restart copyright_year_updater workflow."]
schedule:
# At 03:00 on January 1.
- cron: "0 3 1 1 *"
jobs:
Copyright-year:
runs-on: ubuntu-latest
steps:
- uses: desktop-app/action_code_updater@master
with:
type: "license-year"

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

@@ -5,152 +5,14 @@ on:
types: ["Restart user_agent_updater workflow."]
schedule:
# At 00:00 on day-of-month 1.
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
pull_request_target:
types: [closed]
jobs:
User-agent:
runs-on: ubuntu-latest
env:
codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp"
headBranchPrefix: "chrome_"
baseBranch: "dev"
isPull: "0"
steps:
- name: Set env.
if: startsWith(github.event_name, 'pull_request')
run: |
echo "isPull=1" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v2
- name: Set up git.
run: |
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
if [ -z "${token}" ]; then
echo "Token is unset. Nothing to do."
exit 1
fi
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
git config --global user.email "action@github.com"
git config --global user.name "GitHub Action"
git remote set-url origin $url
- name: Delete branch.
env:
ref: ${{ github.event.pull_request.head.ref }}
if: |
env.isPull == '1'
&& github.event.action == 'closed'
&& startsWith(env.ref, env.headBranchPrefix)
run: |
git push origin --delete $ref
- name: Write a new version of Google Chrome to the user-agent for DNS.
if: env.isPull == '0'
shell: python
run: |
import subprocess, os, re;
regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+";
chrome = "Chrome/";
def newVersion():
output = subprocess.check_output(["google-chrome", "--version"]);
version = re.search(regExpVersion, output);
if not version:
print("Can't find a Chrome version.");
exit();
return version.group(0);
newChromeVersion = newVersion();
print(newChromeVersion);
def setEnv(value):
open(os.environ['GITHUB_ENV'], "a").write(value);
def writeUserAgent():
p = os.environ['codeFile'];
w = open(p, "r");
content = w.read();
w.close();
regExpChrome = chrome + regExpVersion;
version = re.search(regExpChrome, content);
if not version:
print("Can't find an user-agent in the code.");
exit();
content = re.sub(regExpChrome, chrome + newChromeVersion, content);
w = open(p, "w");
w.write(content);
setEnv("ChromeVersion=" + newChromeVersion);
writeUserAgent();
- name: Push to a new branch.
if: env.isPull == '0' && env.ChromeVersion != ''
run: |
git diff > git_diff.txt
if [[ ! -s git_diff.txt ]]; then
echo "Nothing to commit."
exit 0
fi
git checkout -b $headBranchPrefix$ChromeVersion
git add $codeFile
git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion."
git push origin $headBranchPrefix$ChromeVersion
echo "Done!"
- name: Close previous pull requests.
if: env.isPull == '0' && env.ChromeVersion != ''
uses: actions/github-script@0.4.0
- uses: desktop-app/action_code_updater@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const common = {
owner: context.repo.owner,
repo: context.repo.repo,
};
github.pulls.list(common).then(response => {
response.data.forEach((item, _) => {
if (item.head.ref.startsWith(process.env.headBranchPrefix)) {
console.log(`Close ${item.title} #${item.number}.`);
github.pulls.update({
pull_number: item.number,
state: "closed",
...common
});
}
});
});
- name: Create a new pull request.
if: env.isPull == '0' && env.ChromeVersion != ''
uses: actions/github-script@0.4.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const version = process.env.ChromeVersion;
const title = `Update User-Agent for DNS to Chrome ${version}.`;
github.pulls.create({
title: title,
body: "",
head: `${process.env.headBranchPrefix}${version}`,
base: process.env.baseBranch,
owner: context.repo.owner,
repo: context.repo.repo,
});
type: "user-agent"

View File

@@ -17,13 +17,17 @@ The latest version is available for
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
* [Snap](https://snapcraft.io/telegram-desktop)
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
## Old system versions
Version **2.4.4** was the last that supports older systems
* [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg)
* [Linux static build for 32 bit](https://updates.tdesktop.com/tlinux32/tsetup32.2.4.4.tar.xz)
Version **1.8.15** was the last that supports older systems
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))

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
)
@@ -813,6 +812,8 @@ PRIVATE
platform/linux/linux_desktop_environment.h
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_libs.cpp
platform/linux/linux_libs.h
platform/linux/linux_wayland_integration.cpp
@@ -1102,6 +1103,13 @@ if (NOT LINUX)
)
endif()
if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
)
endif()
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.5.1.0" />
Version="2.5.3.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,1,0
PRODUCTVERSION 2,5,1,0
FILEVERSION 2,5,3,0
PRODUCTVERSION 2,5,3,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.1.0"
VALUE "FileVersion", "2.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.1.0"
VALUE "ProductVersion", "2.5.3.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,5,1,0
PRODUCTVERSION 2,5,1,0
FILEVERSION 2,5,3,0
PRODUCTVERSION 2,5,3,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.1.0"
VALUE "FileVersion", "2.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.1.0"
VALUE "ProductVersion", "2.5.3.0"
END
END
BLOCK "VarFileInfo"

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

@@ -358,7 +358,7 @@ bool ParticipantsAdditionalData::canRemoveUser(
if (canRestrictUser(user)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return chat->invitedByMe.contains(user);
return !user->isSelf() && chat->invitedByMe.contains(user);
}
return false;
}
@@ -1212,6 +1212,9 @@ void ParticipantsBoxController::rebuildChatAdmins(
return true;
}();
if (same) {
if (!_allLoaded && !delegate()->peerListFullRowsCount()) {
chatListReady();
}
return;
}

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(
@@ -270,6 +273,8 @@ private:
rpl::event_stream<MuteRequest> _toggleMuteRequests;
rpl::event_stream<not_null<UserData*>> _kickMemberRequests;
rpl::variable<int> _fullCount = 1;
rpl::variable<int> _fullCountMin = 0;
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
not_null<QWidget*> _menuParent;
base::unique_qptr<Ui::PopupMenu> _menu;
@@ -638,10 +643,7 @@ MembersController::MembersController(
}
MembersController::~MembersController() {
if (_menu) {
_menu->setDestroyedCallback(nullptr);
_menu = nullptr;
}
base::take(_menu);
}
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
@@ -682,9 +684,12 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
_realCallRawValue = real;
_realId = real->id();
_fullCount = real->fullCountValue(
) | rpl::map([](int value) {
return std::max(value, 1);
_fullCount = rpl::combine(
real->fullCountValue(),
_fullCountMin.value(),
_fullCountMax.value()
) | rpl::map([](int value, int min, int max) {
return std::max(std::clamp(value, min, max), 1);
});
real->participantsSliceAdded(
@@ -741,18 +746,44 @@ void MembersController::appendInvitedUsers() {
void MembersController::updateRow(
const std::optional<Data::GroupCall::Participant> &was,
const Data::GroupCall::Participant &now) {
auto countChange = 0;
if (const auto row = findRow(now.user)) {
if (now.speaking && (!was || !was->speaking)) {
checkSpeakingRowPosition(row);
}
if (row->state() == Row::State::Invited) {
countChange = 1;
}
updateRow(row, &now);
} else if (auto row = createRow(now)) {
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();
countChange = 1;
}
if (countChange) {
const auto fullCountMin = _fullCountMin.current() + countChange;
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
}
}
@@ -865,10 +896,11 @@ void MembersController::prepare() {
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
const auto call = _call.get();
if (const auto real = _peer->groupCall();
real && call && real->id() == call->id()) {
if (const auto real = _peer->groupCall()
; real && call && real->id() == call->id()) {
prepareRows(real);
} else if (auto row = createSelfRow()) {
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
}
@@ -884,6 +916,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
auto foundSelf = false;
auto changed = false;
const auto &participants = real->participants();
auto fullCountMin = 0;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
@@ -898,6 +931,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
not_null{ user },
&Data::GroupCall::Participant::user);
if (contains) {
++fullCountMin;
++i;
} else {
changed = true;
@@ -913,18 +947,29 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
&Data::GroupCall::Participant::user);
auto row = (i != end(participants)) ? createRow(*i) : createSelfRow();
if (row) {
if (row->state() != Row::State::Invited) {
++fullCountMin;
}
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
for (const auto &participant : participants) {
if (auto row = createRow(participant)) {
++fullCountMin;
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
if (changed) {
delegate()->peerListRefreshRows();
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
if (real->participantsLoaded()) {
_fullCountMax = fullCountMin;
}
}
}
@@ -1002,29 +1047,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 +1071,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 +1178,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

@@ -62,7 +62,7 @@ std::map<int, const char*> BetaLogs() {
"- Fix sticker pack opening.\n"
"- Fix group status display.\n"
"- Fix group members display.\n"
},
{
@@ -75,6 +75,20 @@ 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"
},
{
2005003,
"- Allow using mouse buttons in Push-to-Talk shortcut.\n"
"- Fix blurred thumbnails in Shared Links section.\n"
},
};
};

View File

@@ -270,14 +270,10 @@ std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
Launcher::Launcher(
int argc,
char *argv[],
const QString &deviceModel,
const QString &systemVersion)
char *argv[])
: _argc(argc)
, _argv(argv)
, _baseIntegration(_argc, _argv)
, _deviceModel(deviceModel)
, _systemVersion(systemVersion) {
, _baseIntegration(_argc, _argv) {
base::Integration::Set(&_baseIntegration);
}
@@ -328,6 +324,23 @@ int Launcher::exec() {
// Must be started before Platform is started.
Logs::start(this);
if (Logs::DebugEnabled()) {
const auto openalLogPath = QDir::toNativeSeparators(
cWorkingDir() + qsl("DebugLogs/last_openal_log.txt"));
qputenv("ALSOFT_LOGLEVEL", "3");
#ifdef Q_OS_WIN
_wputenv_s(
L"ALSOFT_LOGFILE",
openalLogPath.toStdWString().c_str());
#else // Q_OS_WIN
qputenv(
"ALSOFT_LOGFILE",
QFile::encodeName(openalLogPath));
#endif // !Q_OS_WIN
}
// Must be started before Sandbox is created.
Platform::start();
Ui::DisableCustomScaling();
@@ -419,14 +432,6 @@ void Launcher::prepareSettings() {
processArguments();
}
QString Launcher::deviceModel() const {
return _deviceModel;
}
QString Launcher::systemVersion() const {
return _systemVersion;
}
uint64 Launcher::installationTag() const {
return InstallationTag;
}

View File

@@ -15,9 +15,7 @@ class Launcher {
public:
Launcher(
int argc,
char *argv[],
const QString &deviceModel,
const QString &systemVersion);
char *argv[]);
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
@@ -26,8 +24,6 @@ public:
QString argumentsString() const;
bool customWorkingDir() const;
QString deviceModel() const;
QString systemVersion() const;
uint64 installationTag() const;
bool checkPortableVersionFolder();
@@ -67,9 +63,6 @@ private:
QStringList _arguments;
BaseIntegration _baseIntegration;
const QString _deviceModel;
const QString _systemVersion;
bool _customWorkingDir = false;
};

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 = 2005001;
constexpr auto AppVersionStr = "2.5.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 2005003;
constexpr auto AppVersionStr = "2.5.3";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -556,7 +556,7 @@ bool InnerWidget::elementUnderCursor(
}
crl::time InnerWidget::elementHighlightTime(
not_null<const HistoryView::Element*> element) {
not_null<const HistoryItem*> item) {
return crl::time(0);
}

View File

@@ -97,7 +97,7 @@ public:
bool elementUnderCursor(
not_null<const HistoryView::Element*> view) override;
crl::time elementHighlightTime(
not_null<const HistoryView::Element*> element) override;
not_null<const HistoryItem*> item) override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null<const HistoryView::Element*> view,

View File

@@ -838,7 +838,7 @@ void GenerateItems(
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
const auto user = history->owner().user(data.vuser_id().v);
const auto userLink = user->createOpenLink();
const auto userLinkText = textcmdLink(1, user->name);
const auto userLinkText = textcmdLink(2, user->name);
auto text = tr::lng_admin_log_muted_participant(
tr::now,
lt_from,
@@ -862,7 +862,7 @@ void GenerateItems(
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
const auto user = history->owner().user(data.vuser_id().v);
const auto userLink = user->createOpenLink();
const auto userLinkText = textcmdLink(1, user->name);
const auto userLinkText = textcmdLink(2, user->name);
auto text = tr::lng_admin_log_unmuted_participant(
tr::now,
lt_from,

View File

@@ -2520,9 +2520,9 @@ void HistoryInner::elementStartStickerLoop(
_animatedStickersPlayed.emplace(view->data());
}
crl::time HistoryInner::elementHighlightTime(not_null<const Element*> view) {
const auto fullAnimMs = _controller->content()->highlightStartTime(
view->data());
crl::time HistoryInner::elementHighlightTime(
not_null<const HistoryItem*> item) {
const auto fullAnimMs = _controller->content()->highlightStartTime(item);
if (fullAnimMs > 0) {
const auto now = crl::now();
if (fullAnimMs < now) {
@@ -3421,8 +3421,8 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
return (App::hoveredItem() == view);
}
crl::time elementHighlightTime(
not_null<const Element*> view) override {
return Instance ? Instance->elementHighlightTime(view) : 0;
not_null<const HistoryItem*> item) override {
return Instance ? Instance->elementHighlightTime(item) : 0;
}
bool elementInSelectionMode() override {
return Instance ? Instance->inSelectionMode() : false;

View File

@@ -84,7 +84,7 @@ public:
int till) const;
void elementStartStickerLoop(not_null<const Element*> view);
[[nodiscard]] crl::time elementHighlightTime(
not_null<const Element*> view);
not_null<const HistoryItem*> item);
void elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context);

View File

@@ -1045,11 +1045,6 @@ void HistoryWidget::scrollToAnimationCallback(
void HistoryWidget::enqueueMessageHighlight(
not_null<HistoryView::Element*> view) {
if (const auto group = session().data().groups().find(view->data())) {
if (const auto leader = group->items.front()->mainView()) {
view = leader;
}
}
auto enqueueMessageId = [this](MsgId universalId) {
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
highlightMessage(universalId);
@@ -1096,7 +1091,7 @@ void HistoryWidget::checkNextHighlight() {
void HistoryWidget::updateHighlightedMessage() {
const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
const auto view = item ? item->mainView() : nullptr;
auto view = item ? item->mainView() : nullptr;
if (!view) {
return stopMessageHighlight();
}
@@ -1105,6 +1100,11 @@ void HistoryWidget::updateHighlightedMessage() {
return stopMessageHighlight();
}
if (const auto group = session().data().groups().find(view->data())) {
if (const auto leader = group->items.front()->mainView()) {
view = leader;
}
}
session().data().requestViewRepaint(view);
}
@@ -1661,6 +1661,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_editMsgId = 0;
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
}
updateCmdStartShown();
updateControlsVisibility();
updateControlsGeometry();
refreshTopBarActiveChat();
@@ -2228,11 +2229,7 @@ void HistoryWidget::updateControlsVisibility() {
_botCommandStart->hide();
} else {
_botKeyboardShow->hide();
if (_cmdStartShown) {
_botCommandStart->show();
} else {
_botCommandStart->hide();
}
_botCommandStart->setVisible(_cmdStartShown);
}
}
_attachToggle->show();
@@ -3738,7 +3735,7 @@ void HistoryWidget::updateSendButtonType() {
bool HistoryWidget::updateCmdStartShown() {
bool cmdStartShown = false;
if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->isBot()))) {
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) {
if (!HasSendText(_field)) {
cmdStartShown = true;
}
@@ -4081,8 +4078,8 @@ void HistoryWidget::checkFieldAutocomplete() {
&& cRecentInlineBots().isEmpty()) {
session().local().readRecentHashtagsAndBots();
} else if (autocomplete.query[0] == '/'
&& _peer->isUser()
&& !_peer->asUser()->isBot()) {
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|| _editMsgId)) {
return;
}
}
@@ -4845,7 +4842,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
_tabbedSelectorToggle->show();
_botKeyboardHide->hide();
_botKeyboardShow->hide();
_botCommandStart->show();
_botCommandStart->setVisible(!_editMsgId);
}
_field->setMaxHeight(computeMaxFieldHeight());
_kbShown = false;
@@ -5652,7 +5649,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
}
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
if (_voiceRecordBar->isListenState()) {
if (_voiceRecordBar->isActive()) {
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
return;
}

View File

@@ -1814,7 +1814,7 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
Expects(_history != nullptr);
Expects(draftKeyCurrent() != Data::DraftKey::None());
if (_voiceRecordBar && _voiceRecordBar->isListenState()) {
if (_voiceRecordBar->isActive()) {
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
return;
}

View File

@@ -924,12 +924,9 @@ CancelButton::CancelButton(not_null<Ui::RpWidget*> parent, int height)
void CancelButton::init() {
_showProgress.value(
) | rpl::start_with_next([=](float64 progress) {
const auto hasProgress = (progress > 0.);
if (isHidden() == !hasProgress) {
setVisible(hasProgress);
}
update();
) | rpl::map(rpl::mappers::_1 > 0.) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool hasProgress) {
setVisible(hasProgress);
}, lifetime());
paintRequest(
@@ -960,6 +957,7 @@ QPoint CancelButton::prepareRippleStartPosition() const {
void CancelButton::requestPaintProgress(float64 progress) {
_showProgress = progress;
update();
}
VoiceRecordBar::VoiceRecordBar(

View File

@@ -79,7 +79,7 @@ bool SimpleElementDelegate::elementUnderCursor(
}
crl::time SimpleElementDelegate::elementHighlightTime(
not_null<const Element*> element) {
not_null<const HistoryItem*> item) {
return crl::time(0);
}
@@ -280,29 +280,44 @@ void Element::refreshDataIdHook() {
void Element::paintHighlight(
Painter &p,
int geometryHeight) const {
const auto animms = delegate()->elementHighlightTime(this);
if (!animms
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
return;
}
const auto top = marginTop();
const auto bottom = marginBottom();
const auto fill = qMin(top, bottom);
const auto skiptop = top - fill;
const auto fillheight = fill + geometryHeight + fill;
const auto dt = (animms > st::activeFadeInDuration)
paintCustomHighlight(p, skiptop, fillheight, data());
}
float64 Element::highlightOpacity(not_null<const HistoryItem*> item) const {
const auto animms = delegate()->elementHighlightTime(item);
if (!animms
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
return 0.;
}
return (animms > st::activeFadeInDuration)
? (1. - (animms - st::activeFadeInDuration)
/ float64(st::activeFadeOutDuration))
: (animms / float64(st::activeFadeInDuration));
}
void Element::paintCustomHighlight(
Painter &p,
int y,
int height,
not_null<const HistoryItem*> item) const {
const auto opacity = highlightOpacity(item);
if (opacity == 0.) {
return;
}
const auto o = p.opacity();
p.setOpacity(o * dt);
p.setOpacity(o * opacity);
p.fillRect(
0,
skiptop,
y,
width(),
fillheight,
height,
st::defaultTextPalette.selectOverlay);
p.setOpacity(o);
}

View File

@@ -51,7 +51,7 @@ public:
Element *replacing = nullptr) = 0;
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
virtual crl::time elementHighlightTime(
not_null<const Element*> element) = 0;
not_null<const HistoryItem*> item) = 0;
virtual bool elementInSelectionMode() = 0;
virtual bool elementIntersectsRange(
not_null<const Element*> view,
@@ -87,7 +87,7 @@ public:
Element *replacing = nullptr) override;
bool elementUnderCursor(not_null<const Element*> view) override;
crl::time elementHighlightTime(
not_null<const Element*> element) override;
not_null<const HistoryItem*> item) override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null<const Element*> view,
@@ -301,6 +301,13 @@ public:
virtual void unloadHeavyPart();
void checkHeavyPart();
void paintCustomHighlight(
Painter &p,
int y,
int height,
not_null<const HistoryItem*> item) const;
float64 highlightOpacity(not_null<const HistoryItem*> item) const;
// Legacy blocks structure.
HistoryBlock *block();
const HistoryBlock *block() const;

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

@@ -482,7 +482,7 @@ void ListWidget::highlightMessage(FullMsgId itemId) {
_highlightedMessageId = itemId;
_highlightTimer.callEach(AnimationTimerDelta);
repaintItem(view);
repaintHighlightedItem(view);
}
}
}
@@ -496,10 +496,24 @@ void ListWidget::showAroundPosition(
refreshViewer();
}
void ListWidget::repaintHighlightedItem(not_null<const Element*> view) {
if (view->isHiddenByGroup()) {
if (const auto group = session().data().groups().find(view->data())) {
if (const auto leader = viewForItem(group->items.front())) {
if (!leader->isHiddenByGroup()) {
repaintItem(leader);
return;
}
}
}
}
repaintItem(view);
}
void ListWidget::updateHighlightedMessage() {
if (const auto item = session().data().message(_highlightedMessageId)) {
if (const auto view = viewForItem(item)) {
repaintItem(view);
repaintHighlightedItem(view);
auto duration = st::activeFadeInDuration + st::activeFadeOutDuration;
if (crl::now() - _highlightStart <= duration) {
return;
@@ -1244,8 +1258,8 @@ bool ListWidget::elementUnderCursor(
}
crl::time ListWidget::elementHighlightTime(
not_null<const HistoryView::Element*> element) {
if (element->data()->fullId() == _highlightedMessageId) {
not_null<const HistoryItem*> item) {
if (item->fullId() == _highlightedMessageId) {
if (_highlightTimer.isActive()) {
return crl::now() - _highlightStart;
}

View File

@@ -221,7 +221,7 @@ public:
Element *replacing = nullptr) override;
bool elementUnderCursor(not_null<const Element*> view) override;
crl::time elementHighlightTime(
not_null<const Element*> element) override;
not_null<const HistoryItem*> item) override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null<const Element*> view,
@@ -340,6 +340,7 @@ private:
int itemTop(not_null<const Element*> view) const;
void repaintItem(FullMsgId itemId);
void repaintItem(const Element *view);
void repaintHighlightedItem(not_null<const Element*> view);
void resizeItem(not_null<Element*> view);
void refreshItem(not_null<const Element*> view);
void itemRemoved(not_null<const HistoryItem*> item);

View File

@@ -570,7 +570,40 @@ void Message::draw(
return;
}
paintHighlight(p, g.height());
auto entry = logEntryOriginal();
auto mediaDisplayed = media && media->isDisplayed();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
? media->getBubbleSelectionIntervals(selection)
: std::vector<BubbleSelectionInterval>();
auto localMediaTop = 0;
const auto customHighlight = mediaDisplayed && media->customHighlight();
if (!mediaSelectionIntervals.empty() || customHighlight) {
auto localMediaBottom = g.top() + g.height();
if (data()->repliesAreComments() || data()->externalReply()) {
localMediaBottom -= st::historyCommentsButtonHeight;
}
if (!mediaOnBottom) {
localMediaBottom -= st::msgPadding.bottom();
}
if (entry) {
localMediaBottom -= entry->height();
}
localMediaTop = localMediaBottom - media->height();
for (auto &[top, height] : mediaSelectionIntervals) {
top += localMediaTop;
}
}
if (customHighlight) {
media->drawHighlight(p, localMediaTop);
} else {
paintHighlight(p, g.height());
}
const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
if (roll) {
@@ -602,34 +635,6 @@ void Message::draw(
fromNameUpdated(g.width());
}
auto entry = logEntryOriginal();
auto mediaDisplayed = media && media->isDisplayed();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
? media->getBubbleSelectionIntervals(selection)
: std::vector<BubbleSelectionInterval>();
if (!mediaSelectionIntervals.empty()) {
auto localMediaBottom = g.top() + g.height();
if (data()->repliesAreComments() || data()->externalReply()) {
localMediaBottom -= st::historyCommentsButtonHeight;
}
if (!mediaOnBottom) {
localMediaBottom -= st::msgPadding.bottom();
}
if (entry) {
localMediaBottom -= entry->height();
}
const auto localMediaTop = localMediaBottom - media->height();
for (auto &[top, height] : mediaSelectionIntervals) {
top += localMediaTop;
}
}
auto skipTail = isAttachedToNext()
|| (media && media->skipBubbleTail())
|| (keyboard != nullptr)
@@ -795,8 +800,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 +833,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 +2139,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

@@ -951,6 +951,7 @@ void Document::drawGrouped(
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
p.translate(geometry.topLeft());

View File

@@ -72,6 +72,7 @@ public:
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
TextState getStateGrouped(

View File

@@ -901,6 +901,7 @@ void Gif::drawGrouped(
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
ensureDataMediaCreated();
@@ -989,8 +990,16 @@ void Gif::drawGrouped(
p.drawPixmap(geometry, *cache);
}
if (selected) {
const auto overlayOpacity = selected
? (1. - highlightOpacity)
: highlightOpacity;
if (overlayOpacity > 0.) {
p.setOpacity(overlayOpacity);
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
if (!selected) {
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
}
p.setOpacity(1.);
}
if (radial

View File

@@ -79,6 +79,7 @@ public:
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
TextState getStateGrouped(

View File

@@ -84,6 +84,8 @@ public:
}
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
}
virtual void drawHighlight(Painter &p, int top) const {
}
virtual void draw(
Painter &p,
const QRect &r,
@@ -177,6 +179,7 @@ public:
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
Unexpected("Grouping method call.");
@@ -274,6 +277,9 @@ public:
const QRect &bubble,
crl::time ms) const {
}
[[nodiscard]] virtual bool customHighlight() const {
return false;
}
virtual bool hasHeavyPart() const {
return false;

View File

@@ -268,6 +268,18 @@ QMargins GroupedMedia::groupedPadding() const {
(normal.bottom() - grouped.bottom()) + addToBottom);
}
void GroupedMedia::drawHighlight(Painter &p, int top) const {
if (_mode != Mode::Column) {
return;
}
const auto skip = top + groupedPadding().top();
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
const auto rect = part.geometry.translated(0, skip);
_parent->paintCustomHighlight(p, rect.y(), rect.height(), part.item);
}
}
void GroupedMedia::draw(
Painter &p,
const QRect &clip,
@@ -290,6 +302,9 @@ void GroupedMedia::draw(
if (textSelection) {
selection = part.content->skipSelection(selection);
}
const auto highlightOpacity = (_mode == Mode::Grid)
? _parent->highlightOpacity(part.item)
: 0.;
part.content->drawGrouped(
p,
clip,
@@ -298,6 +313,7 @@ void GroupedMedia::draw(
part.geometry.translated(0, groupPadding.top()),
part.sides,
cornersFromSides(part.sides),
highlightOpacity,
&part.cacheKey,
&part.cache);
}

View File

@@ -31,6 +31,7 @@ public:
void refreshParentId(not_null<HistoryItem*> realParent) override;
void drawHighlight(Painter &p, int top) const override;
void draw(
Painter &p,
const QRect &clip,
@@ -87,6 +88,9 @@ public:
bool allowsFastShare() const override {
return true;
}
bool customHighlight() const override {
return true;
}
void stopAnimation() override;
void checkAnimation() override;

View File

@@ -485,6 +485,7 @@ void Photo::drawGrouped(
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
ensureDataMediaCreated();
@@ -509,9 +510,18 @@ void Photo::drawGrouped(
// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
}
p.drawPixmap(geometry.topLeft(), *cache);
if (selected) {
const auto overlayOpacity = selected
? (1. - highlightOpacity)
: highlightOpacity;
if (overlayOpacity > 0.) {
p.setOpacity(overlayOpacity);
const auto roundRadius = ImageRoundRadius::Large;
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
if (!selected) {
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
}
p.setOpacity(1.);
}
const auto displayState = radial

View File

@@ -68,6 +68,7 @@ public:
const QRect &geometry,
RectParts sides,
RectParts corners,
float64 highlightOpacity,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
TextState getStateGrouped(

View File

@@ -247,7 +247,7 @@ rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) {
) | rpl::map([=] {
return chat->participants.empty()
? 0
: int(chat->admins.size() + 1); // + creator
: int(chat->admins.size() + (chat->creator ? 1 : 0));
});
} else if (const auto channel = peer->asChannel()) {
return peer->session().changes().peerFlagsValue(

View File

@@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_account.h"
#include "base/platform/base_platform_info.h"
#include "core/application.h"
#include "core/launcher.h"
#include "core/shortcuts.h"
#include "storage/storage_account.h"
#include "storage/storage_domain.h" // Storage::StartResult.
@@ -386,8 +386,8 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
auto fields = base::take(_mtpFields);
fields.config = std::move(config);
fields.deviceModel = Core::App().launcher()->deviceModel();
fields.systemVersion = Core::App().launcher()->systemVersion();
fields.deviceModel = Platform::DeviceModelPretty();
fields.systemVersion = Platform::SystemVersionPretty();
_mtp = std::make_unique<MTP::Instance>(
MTP::Instance::Mode::Normal,
std::move(fields));
@@ -534,8 +534,8 @@ void Account::destroyMtpKeys(MTP::AuthKeysList &&keys) {
destroyFields.mainDcId = MTP::Instance::Fields::kNoneMainDc;
destroyFields.config = std::make_unique<MTP::Config>(_mtp->config());
destroyFields.keys = std::move(keys);
destroyFields.deviceModel = Core::App().launcher()->deviceModel();
destroyFields.systemVersion = Core::App().launcher()->systemVersion();
destroyFields.deviceModel = Platform::DeviceModelPretty();
destroyFields.systemVersion = Platform::SystemVersionPretty();
_mtpForKeysDestroy = std::make_unique<MTP::Instance>(
MTP::Instance::Mode::KeysDestroyer,
std::move(destroyFields));

View File

@@ -2443,7 +2443,9 @@ bool OverlayWidget::initStreaming(bool continueStreaming) {
void OverlayWidget::startStreamingPlayer() {
Expects(_streamed != nullptr);
if (_streamed->instance.player().playing()) {
if (!_streamed->instance.player().paused()
&& !_streamed->instance.player().finished()
&& !_streamed->instance.player().failed()) {
if (!_streamed->withSound) {
return;
}

View File

@@ -1479,9 +1479,8 @@ Link::Link(
int32 tw = 0, th = 0;
if (_page && _page->photo) {
const auto photo = _page->photo;
if (photo->inlineThumbnailBytes().isEmpty()
&& (photo->hasExact(Data::PhotoSize::Small)
|| photo->hasExact(Data::PhotoSize::Thumbnail))) {
if (photo->hasExact(Data::PhotoSize::Small)
|| photo->hasExact(Data::PhotoSize::Thumbnail)) {
photo->load(Data::PhotoSize::Small, parent->fullId());
}
tw = style::ConvertScale(photo->width());
@@ -1623,7 +1622,7 @@ void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const P
}
void Link::validateThumbnail() {
if (!_thumbnail.isNull()) {
if (!_thumbnail.isNull() && !_thumbnailBlurred) {
return;
}
if (_page && _page->photo) {
@@ -1631,12 +1630,16 @@ void Link::validateThumbnail() {
ensurePhotoMediaCreated();
if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
_thumbnailBlurred = false;
} else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
_thumbnail = large->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
_thumbnailBlurred = false;
} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
_thumbnail = small->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
_thumbnailBlurred = false;
} else if (const auto blurred = _photoMedia->thumbnailInline()) {
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
return;
} else {
return;
}
@@ -1644,14 +1647,20 @@ void Link::validateThumbnail() {
delegate()->unregisterHeavyItem(this);
} else if (_page && _page->document && _page->document->hasThumbnail()) {
ensureDocumentMediaCreated();
const auto roundRadius = _page->document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (const auto thumbnail = _documentMedia->thumbnail()) {
auto roundRadius = _page->document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
_documentMedia = nullptr;
delegate()->unregisterHeavyItem(this);
_thumbnailBlurred = false;
} else if (const auto blurred = _documentMedia->thumbnailInline()) {
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
return;
} else {
return;
}
_documentMedia = nullptr;
delegate()->unregisterHeavyItem(this);
} else {
const auto size = QSize(st::linksPhotoSize, st::linksPhotoSize);
_thumbnail = QPixmap(size * cIntRetinaFactor());
@@ -1683,6 +1692,7 @@ void Link::validateThumbnail() {
_letter,
style::al_center);
}
_thumbnailBlurred = false;
}
}

View File

@@ -368,6 +368,7 @@ private:
int _pixh = 0;
Ui::Text::String _text = { st::msgMinWidth };
QPixmap _thumbnail;
bool _thumbnailBlurred = true;
struct LinkEntry {
LinkEntry() : width(0) {

View File

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "platform/linux/launcher_linux.h"
#include "base/platform/base_platform_info.h"
#include "platform/linux/specific_linux.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
@@ -46,7 +45,7 @@ private:
} // namespace
Launcher::Launcher(int argc, char *argv[])
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
: Core::Launcher(argc, argv) {
}
void Launcher::initHook() {

View File

@@ -0,0 +1,182 @@
/*
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 "platform/linux/linux_gsd_media_keys.h"
#include "core/sandbox.h"
#include "media/player/media_player_instance.h"
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
extern "C" {
#undef signals
#include <gio/gio.h>
#define signals public
} // extern "C"
namespace Platform {
namespace internal {
namespace {
constexpr auto kDBusTimeout = 30000;
constexpr auto kService = "org.gnome.SettingsDaemon.MediaKeys"_cs;
constexpr auto kOldService = "org.gnome.SettingsDaemon"_cs;
constexpr auto kMATEService = "org.mate.SettingsDaemon"_cs;
constexpr auto kObjectPath = "/org/gnome/SettingsDaemon/MediaKeys"_cs;
constexpr auto kMATEObjectPath = "/org/mate/SettingsDaemon/MediaKeys"_cs;
constexpr auto kInterface = kService;
constexpr auto kMATEInterface = "org.mate.SettingsDaemon.MediaKeys"_cs;
void KeyPressed(
GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data) {
gchar *appUtf8;
gchar *keyUtf8;
g_variant_get(parameters, "(ss)", &appUtf8, &keyUtf8);
const auto app = QString::fromUtf8(appUtf8);
const auto key = QString::fromUtf8(keyUtf8);
g_free(keyUtf8);
g_free(appUtf8);
if (app != QCoreApplication::applicationName()) {
return;
}
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
if (key == qstr("Play")) {
Media::Player::instance()->playPause();
} else if (key == qstr("Stop")) {
Media::Player::instance()->stop();
} else if (key == qstr("Next")) {
Media::Player::instance()->next();
} else if (key == qstr("Previous")) {
Media::Player::instance()->previous();
}
});
}
} // namespace
GSDMediaKeys::GSDMediaKeys() {
GError *error = nullptr;
const auto interface = QDBusConnection::sessionBus().interface();
if (!interface) {
return;
}
if (interface->isServiceRegistered(kService.utf16())) {
_service = kService.utf16();
_objectPath = kObjectPath.utf16();
_interface = kInterface.utf16();
} else if (interface->isServiceRegistered(kOldService.utf16())) {
_service = kOldService.utf16();
_objectPath = kObjectPath.utf16();
_interface = kInterface.utf16();
} else if (interface->isServiceRegistered(kMATEService.utf16())) {
_service = kMATEService.utf16();
_objectPath = kMATEObjectPath.utf16();
_interface = kMATEInterface.utf16();
} else {
return;
}
_dbusConnection = g_bus_get_sync(
G_BUS_TYPE_SESSION,
nullptr,
&error);
if (error) {
LOG(("GSD Media Keys Error: %1").arg(error->message));
g_error_free(error);
return;
}
auto reply = g_dbus_connection_call_sync(
_dbusConnection,
_service.toUtf8(),
_objectPath.toUtf8(),
_interface.toUtf8(),
"GrabMediaPlayerKeys",
g_variant_new(
"(su)",
QCoreApplication::applicationName().toUtf8().constData(),
0),
nullptr,
G_DBUS_CALL_FLAGS_NONE,
kDBusTimeout,
nullptr,
&error);
if (!error) {
_grabbed = true;
g_variant_unref(reply);
} else {
LOG(("GSD Media Keys Error: %1").arg(error->message));
g_error_free(error);
}
_signalId = g_dbus_connection_signal_subscribe(
_dbusConnection,
_service.toUtf8(),
_interface.toUtf8(),
"MediaPlayerKeyPressed",
_objectPath.toUtf8(),
nullptr,
G_DBUS_SIGNAL_FLAGS_NONE,
KeyPressed,
nullptr,
nullptr);
}
GSDMediaKeys::~GSDMediaKeys() {
GError *error = nullptr;
if (_signalId != 0) {
g_dbus_connection_signal_unsubscribe(
_dbusConnection,
_signalId);
}
if (_grabbed) {
auto reply = g_dbus_connection_call_sync(
_dbusConnection,
_service.toUtf8(),
_objectPath.toUtf8(),
_interface.toUtf8(),
"ReleaseMediaPlayerKeys",
g_variant_new(
"(s)",
QCoreApplication::applicationName().toUtf8().constData()),
nullptr,
G_DBUS_CALL_FLAGS_NONE,
kDBusTimeout,
nullptr,
&error);
if (!error) {
_grabbed = false;
g_variant_unref(reply);
} else {
LOG(("GSD Media Keys Error: %1").arg(error->message));
g_error_free(error);
}
}
if (_dbusConnection) {
g_object_unref(_dbusConnection);
}
}
} // namespace internal
} // namespace Platform

View File

@@ -0,0 +1,36 @@
/*
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
typedef struct _GDBusConnection GDBusConnection;
namespace Platform {
namespace internal {
class GSDMediaKeys {
public:
GSDMediaKeys();
GSDMediaKeys(const GSDMediaKeys &other) = delete;
GSDMediaKeys &operator=(const GSDMediaKeys &other) = delete;
GSDMediaKeys(GSDMediaKeys &&other) = delete;
GSDMediaKeys &operator=(GSDMediaKeys &&other) = delete;
~GSDMediaKeys();
private:
GDBusConnection *_dbusConnection = nullptr;
QString _service;
QString _objectPath;
QString _interface;
uint _signalId = 0;
bool _grabbed = false;
};
} // namespace internal
} // namespace Platform

View File

@@ -23,9 +23,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "media/player/media_player_instance.h"
#include "media/audio/media_audio.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_xcb_utilities_linux.h"
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "platform/linux/linux_gsd_media_keys.h"
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "base/call_delayed.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/input_fields.h"
#include "facades.h"
#include "app.h"
@@ -34,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include <QtCore/QTemporaryFile>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
@@ -41,9 +48,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusReply>
#include <QtDBus/QDBusError>
#include <QtDBus/QDBusObjectPath>
#include <QtDBus/QDBusMetaType>
#include <xcb/xcb.h>
#include <statusnotifieritem.h>
#include <dbusmenuexporter.h>
extern "C" {
#undef signals
@@ -58,6 +67,7 @@ namespace {
constexpr auto kPanelTrayIconName = "telegram-panel"_cs;
constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs;
constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs;
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
constexpr auto kTrayIconFilename = "tdesktop-trayicon-XXXXXX.png"_cs;
@@ -69,6 +79,8 @@ constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs;
constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs;
constexpr auto kAppMenuInterface = kAppMenuService;
constexpr auto kMainMenuObjectPath = "/MenuBar"_cs;
bool TrayIconMuted = true;
int32 TrayIconCount = 0;
base::flat_map<int, QImage> TrayIconImageBack;
@@ -209,6 +221,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 +237,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 +255,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 +352,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 +386,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();
@@ -419,7 +455,7 @@ bool IsAppMenuSupported() {
return interface->isServiceRegistered(kAppMenuService.utf16());
}
void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
void RegisterAppMenu(uint winId, const QString &menuPath) {
auto message = QDBusMessage::createMethodCall(
kAppMenuService.utf16(),
kAppMenuObjectPath.utf16(),
@@ -428,7 +464,7 @@ void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
message.setArguments({
winId,
QVariant::fromValue(menuPath)
QVariant::fromValue(QDBusObjectPath(menuPath))
});
QDBusConnection::sessionBus().send(message);
@@ -548,6 +584,17 @@ void MainWindow::initHook() {
} else {
LOG(("Not using Unity launcher counter."));
}
Media::Player::instance()->updatedNotifier(
) | rpl::start_with_next([=](const Media::Player::TrackState &state) {
if (!Media::Player::IsStoppedOrStopping(state.state)) {
if (!_gsdMediaKeys) {
_gsdMediaKeys = std::make_unique<internal::GSDMediaKeys>();
}
} else if (_gsdMediaKeys) {
_gsdMediaKeys = nullptr;
}
}, lifetime());
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
updateWaylandDecorationColors();
@@ -716,8 +763,8 @@ void MainWindow::handleAppMenuOwnerChanged(
LOG(("Not using D-Bus global menu."));
}
if (_appMenuSupported && !_mainMenuPath.path().isEmpty()) {
RegisterAppMenu(winId(), _mainMenuPath);
if (_appMenuSupported && _mainMenuExporter) {
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
} else {
UnregisterAppMenu(winId());
}
@@ -1032,14 +1079,12 @@ void MainWindow::createGlobalMenu() {
about->setMenuRole(QAction::AboutQtRole);
_mainMenuPath.setPath(qsl("/MenuBar"));
_mainMenuExporter = new DBusMenuExporter(
_mainMenuPath.path(),
kMainMenuObjectPath.utf16(),
psMainMenu);
if (_appMenuSupported) {
RegisterAppMenu(winId(), _mainMenuPath);
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
}
updateGlobalMenu();
@@ -1171,9 +1216,9 @@ void MainWindow::handleVisibleChangedHook(bool visible) {
}
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (_appMenuSupported && !_mainMenuPath.path().isEmpty()) {
if (_appMenuSupported && _mainMenuExporter) {
if (visible) {
RegisterAppMenu(winId(), _mainMenuPath);
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
} else {
UnregisterAppMenu(winId());
}

View File

@@ -9,13 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_main_window.h"
#include "ui/widgets/popup_menu.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "statusnotifieritem.h"
#include <QtCore/QTemporaryFile>
#include <QtDBus/QDBusObjectPath>
#include <dbusmenuexporter.h>
class QTemporaryFile;
class DBusMenuExporter;
class StatusNotifierItem;
typedef void* gpointer;
typedef char gchar;
@@ -24,6 +25,11 @@ typedef struct _GDBusProxy GDBusProxy;
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
namespace Platform {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
namespace internal {
class GSDMediaKeys;
} // namespace internal
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
class MainWindow : public Window::MainWindow {
public:
@@ -82,11 +88,12 @@ private:
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
StatusNotifierItem *_sniTrayIcon = nullptr;
GDBusProxy *_sniDBusProxy = nullptr;
std::unique_ptr<QTemporaryFile> _trayIconFile = nullptr;
std::unique_ptr<QTemporaryFile> _trayIconFile;
bool _appMenuSupported = false;
DBusMenuExporter *_mainMenuExporter = nullptr;
QDBusObjectPath _mainMenuPath;
std::unique_ptr<internal::GSDMediaKeys> _gsdMediaKeys;
QMenu *psMainMenu = nullptr;
QAction *psLogout = nullptr;

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>
@@ -452,25 +451,6 @@ bool ShowXCBWindowMenu(QWindow *window) {
return true;
}
bool XCBFrameExtentsSupported() {
const auto connection = base::Platform::XCB::GetConnectionFromQt();
if (!connection) {
return false;
}
const auto frameExtentsAtom = base::Platform::XCB::GetAtom(
connection,
kXCBFrameExtentsAtomName.utf16());
if (!frameExtentsAtom.has_value()) {
return false;
}
return ranges::contains(
base::Platform::XCB::GetWMSupported(connection),
*frameExtentsAtom);
}
bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) {
const auto connection = base::Platform::XCB::GetConnectionFromQt();
if (!connection) {
@@ -527,25 +507,6 @@ bool UnsetXCBFrameExtents(QWindow *window) {
return true;
}
bool XCBSkipTaskbarSupported() {
const auto connection = base::Platform::XCB::GetConnectionFromQt();
if (!connection) {
return false;
}
const auto skipTaskbarAtom = base::Platform::XCB::GetAtom(
connection,
"_NET_WM_STATE_SKIP_TASKBAR");
if (!skipTaskbarAtom.has_value()) {
return false;
}
return ranges::contains(
base::Platform::XCB::GetWMSupported(connection),
*skipTaskbarAtom);
}
Window::Control GtkKeywordToWindowControl(const QString &keyword) {
if (keyword == qstr("minimize")) {
return Window::Control::Minimize;
@@ -805,7 +766,8 @@ bool TrayIconSupported() {
}
bool SkipTaskbarSupported() {
return !IsWayland() && XCBSkipTaskbarSupported();
return !IsWayland()
&& base::Platform::XCB::IsSupportedByWM("_NET_WM_STATE_SKIP_TASKBAR");
}
bool StartSystemMove(QWindow *window) {
@@ -849,11 +811,8 @@ bool UnsetWindowExtents(QWindow *window) {
}
bool WindowsNeedShadow() {
if (!IsWayland() && XCBFrameExtentsSupported()) {
return true;
}
return false;
return !IsWayland()
&& base::Platform::XCB::IsSupportedByWM(kXCBFrameExtentsAtomName.utf16());
}
Window::ControlsLayout WindowControlsLayout() {

View File

@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/base_platform_file_utilities.h"
#include "base/platform/mac/base_utilities_mac.h"
@@ -20,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
Launcher::Launcher(int argc, char *argv[])
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
: Core::Launcher(argc, argv) {
}
void Launcher::initHook() {

View File

@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_h.h"
#include <shellapi.h>
@@ -18,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
Launcher::Launcher(int argc, char *argv[])
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
: Core::Launcher(argc, argv) {
}
std::optional<QStringList> Launcher::readArgumentsHook(

View File

@@ -217,7 +217,11 @@ void MainWindow::psSetupTrayIcon() {
auto icon = QIcon(App::pixmapFromImageInPlace(Core::App().logoNoMargin()));
trayIcon->setIcon(icon);
connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray()));
connect(
trayIcon,
&QSystemTrayIcon::messageClicked,
this,
[=] { App::wnd()->showFromTray(); });
attachToTrayIcon(trayIcon);
}
updateIconCounters();

View File

@@ -73,10 +73,18 @@ object_ptr<Ui::RpWidget> CreateIntroSettings(
SetupUpdate(result);
AddSkip(result);
}
AddDivider(result);
AddSkip(result);
SetupSystemIntegrationContent(result);
AddSkip(result);
{
auto wrap = object_ptr<Ui::VerticalLayout>(result);
SetupSystemIntegrationContent(wrap.data());
if (wrap->count() > 0) {
AddDivider(result);
AddSkip(result);
result->add(object_ptr<Ui::OverrideMargins>(
result,
std::move(wrap)));
AddSkip(result);
}
}
AddDivider(result);
AddSkip(result);
SetupInterfaceScale(result, false);

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 2005001
AppVersion 2005003
AppVersionStrMajor 2.5
AppVersionStrSmall 2.5.1
AppVersionStr 2.5.1
BetaChannel 0
AppVersionStrSmall 2.5.3
AppVersionStr 2.5.3
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 2.5.1
AppVersionOriginal 2.5.3.beta

View File

@@ -28,6 +28,8 @@ PRIVATE
if (NOT DESKTOP_APP_DISABLE_WEBRTC_INTEGRATION)
nice_target_sources(lib_tgcalls ${tgcalls_loc}
PRIVATE
AudioDeviceHelper.cpp
AudioDeviceHelper.h
CodecSelectHelper.cpp
CodecSelectHelper.h
CryptoHelper.cpp

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,14 @@
2.5.3 beta (30.12.20)
- Allow using mouse buttons in Push-to-Talk shortcut.
- Fix blurred thumbnails in Shared Links section.
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
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