Updated to 6.7.7.

Esse commit está contido em:
23rd
2026-04-21 06:47:56 +03:00
149 arquivos alterados com 7044 adições e 643 exclusões
+2 -2
Ver Arquivo
@@ -127,7 +127,7 @@ Locate the render tool (`codegen_style` with `--render-svg` mode):
```bash ```bash
if [[ "$OSTYPE" == darwin* ]]; then if [[ "$OSTYPE" == darwin* ]]; then
ls out/Debug/codegen_style ls out/Telegram/codegen/codegen/style/Debug/codegen_style
else else
ls out/Telegram/codegen/codegen/style/Debug/codegen_style.exe ls out/Telegram/codegen/codegen/style/Debug/codegen_style.exe
fi fi
@@ -137,7 +137,7 @@ If missing, build it: `cmake --build out --config Debug --target codegen_style`
Test on a known good SVG (use the appropriate binary path for the OS): Test on a known good SVG (use the appropriate binary path for the OS):
```bash ```bash
CODEGEN=$(if [[ "$OSTYPE" == darwin* ]]; then echo out/Debug/codegen_style; else echo out/Telegram/codegen/codegen/style/Debug/codegen_style.exe; fi) CODEGEN=$(if [[ "$OSTYPE" == darwin* ]]; then echo out/Telegram/codegen/codegen/style/Debug/codegen_style; else echo out/Telegram/codegen/codegen/style/Debug/codegen_style.exe; fi)
$CODEGEN --render-svg Telegram/Resources/icons/menu/tag_add.svg .ai/icon_{name}/test_render.png 512 $CODEGEN --render-svg Telegram/Resources/icons/menu/tag_add.svg .ai/icon_{name}/test_render.png 512
``` ```
+1 -1
Ver Arquivo
@@ -44,4 +44,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deploy id: deploy
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v5
+12
Ver Arquivo
@@ -196,6 +196,8 @@ PRIVATE
api/api_statistics_data_deserialize.h api/api_statistics_data_deserialize.h
api/api_statistics_sender.cpp api/api_statistics_sender.cpp
api/api_statistics_sender.h api/api_statistics_sender.h
api/api_stickers_creator.cpp
api/api_stickers_creator.h
api/api_suggest_post.cpp api/api_suggest_post.cpp
api/api_suggest_post.h api/api_suggest_post.h
api/api_text_entities.cpp api/api_text_entities.cpp
@@ -361,6 +363,8 @@ PRIVATE
boxes/send_gif_with_caption_box.h boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp boxes/send_files_box.cpp
boxes/send_files_box.h boxes/send_files_box.h
boxes/send_files_box_reply_header.cpp
boxes/send_files_box_reply_header.h
boxes/share_box.cpp boxes/share_box.cpp
boxes/share_box.h boxes/share_box.h
boxes/star_gift_auction_box.cpp boxes/star_gift_auction_box.cpp
@@ -377,6 +381,8 @@ PRIVATE
boxes/star_gift_preview_box.h boxes/star_gift_preview_box.h
boxes/star_gift_resale_box.cpp boxes/star_gift_resale_box.cpp
boxes/star_gift_resale_box.h boxes/star_gift_resale_box.h
boxes/sticker_creator_box.cpp
boxes/sticker_creator_box.h
boxes/sticker_set_box.cpp boxes/sticker_set_box.cpp
boxes/sticker_set_box.h boxes/sticker_set_box.h
boxes/stickers_box.cpp boxes/stickers_box.cpp
@@ -474,6 +480,8 @@ PRIVATE
chat_helpers/emoji_keywords.h chat_helpers/emoji_keywords.h
chat_helpers/emoji_list_widget.cpp chat_helpers/emoji_list_widget.cpp
chat_helpers/emoji_list_widget.h chat_helpers/emoji_list_widget.h
chat_helpers/emoji_picker_overlay.cpp
chat_helpers/emoji_picker_overlay.h
chat_helpers/emoji_sets_manager.cpp chat_helpers/emoji_sets_manager.cpp
chat_helpers/emoji_sets_manager.h chat_helpers/emoji_sets_manager.h
chat_helpers/emoji_suggestions_widget.cpp chat_helpers/emoji_suggestions_widget.cpp
@@ -510,6 +518,8 @@ PRIVATE
chat_helpers/ttl_media_layer_widget.h chat_helpers/ttl_media_layer_widget.h
core/application.cpp core/application.cpp
core/application.h core/application.h
core/proxy_rotation_manager.cpp
core/proxy_rotation_manager.h
core/cached_webview_availability.h core/cached_webview_availability.h
core/bank_card_click_handler.cpp core/bank_card_click_handler.cpp
core/bank_card_click_handler.h core/bank_card_click_handler.h
@@ -1457,6 +1467,8 @@ PRIVATE
mtproto/facade.h mtproto/facade.h
mtproto/mtp_instance.cpp mtproto/mtp_instance.cpp
mtproto/mtp_instance.h mtproto/mtp_instance.h
mtproto/proxy_check.cpp
mtproto/proxy_check.h
mtproto/sender.h mtproto/sender.h
mtproto/session.cpp mtproto/session.cpp
mtproto/session.h mtproto/session.h
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 685 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.1 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.6 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 769 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.3 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.9 KiB

+45
Ver Arquivo
@@ -1285,6 +1285,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_use_system_settings" = "Use system proxy settings"; "lng_proxy_use_system_settings" = "Use system proxy settings";
"lng_proxy_use_custom" = "Use custom proxy"; "lng_proxy_use_custom" = "Use custom proxy";
"lng_proxy_use_for_calls" = "Use proxy for calls"; "lng_proxy_use_for_calls" = "Use proxy for calls";
"lng_proxy_auto_switch" = "Auto-switch proxies";
"lng_proxy_auto_switch_about" = "You can choose how quickly the app should auto-connect to the nearest active proxy if the current one stops working.";
"lng_proxy_auto_switch_timeout#one" = "{count} s";
"lng_proxy_auto_switch_timeout#other" = "{count} s";
"lng_proxy_about" = "Proxy servers may be helpful in accessing Telegram if there is no connection in a specific region."; "lng_proxy_about" = "Proxy servers may be helpful in accessing Telegram if there is no connection in a specific region.";
"lng_proxy_add" = "Add proxy"; "lng_proxy_add" = "Add proxy";
"lng_proxy_share" = "Share"; "lng_proxy_share" = "Share";
@@ -4545,10 +4549,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stickers_context_edit_name" = "Edit name"; "lng_stickers_context_edit_name" = "Edit name";
"lng_stickers_context_delete" = "Delete sticker"; "lng_stickers_context_delete" = "Delete sticker";
"lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?"; "lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?";
"lng_stickers_context_delete_pack" = "Delete Pack";
"lng_stickers_context_delete_pack_everyone" = "Delete for Everyone";
"lng_stickers_context_delete_pack_self" = "Delete for Myself";
"lng_stickers_delete_pack_sure" = "Are you sure you want to delete this sticker set for everyone? This cannot be undone.";
"lng_stickers_bot_more_options" = "Check the {bot} bot for more options";
"lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; "lng_stickers_box_edit_name_title" = "Edit Sticker Set Name";
"lng_stickers_box_edit_name_about" = "Choose a name for your set."; "lng_stickers_box_edit_name_about" = "Choose a name for your set.";
"lng_stickers_creator_badge" = "edit"; "lng_stickers_creator_badge" = "edit";
"lng_stickers_create_new" = "Create a New Sticker";
"lng_stickers_add_existing" = "Add an Existing Sticker";
"lng_stickers_add_to_set" = "Add to Sticker Set";
"lng_stickers_already_in_set" = "This Sticker is already in the Set.";
"lng_stickers_set_is_full" = "This Sticker Set is full.";
"lng_emoji_add_to_set" = "Add to Emoji Set";
"lng_emoji_already_in_set" = "This Emoji is already in the Set.";
"lng_emoji_set_is_full" = "This Emoji Set is full.";
"lng_emoji_added" = "Emoji added.";
"lng_stickers_pack_choose_emoji_title" = "Choose Emoji";
"lng_stickers_pack_choose_emoji_about" = "Pick an emoji that corresponds to this sticker.";
"lng_stickers_pick_existing_title" = "Choose Sticker";
"lng_stickers_pick_existing_about" = "Pick a sticker from your library to add it to this set.";
"lng_stickers_pick_existing_empty" = "You don't have any stickers yet.";
"lng_stickers_create_image_title" = "New Sticker";
"lng_stickers_create_image_about" = "Choose an image to add as a sticker.";
"lng_stickers_create_choose_image" = "Choose Image";
"lng_stickers_create_image_filter" = "Images";
"lng_stickers_create_open_failed" = "Could not load image.";
"lng_stickers_create_too_small" = "The image must be at least {size} pixels on each side.";
"lng_stickers_create_choose_emoji" = "Choose an emoji that corresponds to your sticker:";
"lng_stickers_create_emoji_about" = "Choose emojis that match your sticker";
"lng_stickers_create_emoji_required" = "Please choose an emoji.";
"lng_stickers_create_uploading" = "Uploading sticker…";
"lng_stickers_create_upload_failed" = "Sticker upload failed. Please try again.";
"lng_stickers_create_added" = "Sticker added.";
"lng_in_dlg_photo" = "Photo"; "lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album"; "lng_in_dlg_album" = "Album";
"lng_in_dlg_video" = "Video"; "lng_in_dlg_video" = "Video";
@@ -5655,6 +5691,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mediaview_playback_speed" = "Playback speed: {speed}"; "lng_mediaview_playback_speed" = "Playback speed: {speed}";
"lng_mediaview_rotate_video" = "Rotate video"; "lng_mediaview_rotate_video" = "Rotate video";
"lng_mediaview_quality_auto" = "Auto"; "lng_mediaview_quality_auto" = "Auto";
"lng_mediaview_quality_original" = "Original ({quality}p)";
"lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_title" = "Theme Preview";
"lng_theme_preview_generating" = "Generating color theme preview..."; "lng_theme_preview_generating" = "Generating color theme preview...";
@@ -7228,6 +7265,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_photo_editor_menu_flip" = "Flip"; "lng_photo_editor_menu_flip" = "Flip";
"lng_photo_editor_menu_duplicate" = "Duplicate"; "lng_photo_editor_menu_duplicate" = "Duplicate";
"lng_photo_editor_text_style_plain" = "Plain";
"lng_photo_editor_text_style_framed" = "Framed";
"lng_photo_editor_text_style_semi_transparent" = "Semi-Transparent";
"lng_photo_editor_crop_original" = "Original"; "lng_photo_editor_crop_original" = "Original";
"lng_photo_editor_crop_square" = "Square"; "lng_photo_editor_crop_square" = "Square";
"lng_photo_editor_crop_free" = "Free"; "lng_photo_editor_crop_free" = "Free";
@@ -7964,4 +8005,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mac_hold_to_quit" = "Hold {text} to Quit"; "lng_mac_hold_to_quit" = "Hold {text} to Quit";
"lng_sr_country_column_name" = "Country name";
"lng_sr_languages_column_native" = "Native name";
"lng_sr_languages_column_name" = "Language name";
// Keys finished // Keys finished
+1 -1
Ver Arquivo
@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="6.7.6.0" /> Version="6.7.7.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
+4 -4
Ver Arquivo
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,7,6,0 FILEVERSION 6,7,7,0
PRODUCTVERSION 6,7,6,0 PRODUCTVERSION 6,7,7,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "" VALUE "CompanyName", ""
VALUE "FileDescription", "Telegram Desktop" VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "6.7.6.0" VALUE "FileVersion", "6.7.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "LegalCopyright", "Copyright (C) 2014-2026"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.7.6.0" VALUE "ProductVersion", "6.7.7.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"
+4 -4
Ver Arquivo
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,7,6,0 FILEVERSION 6,7,7,0
PRODUCTVERSION 6,7,6,0 PRODUCTVERSION 6,7,7,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "" VALUE "CompanyName", ""
VALUE "FileDescription", "Telegram Desktop Updater" VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "6.7.6.0" VALUE "FileVersion", "6.7.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "LegalCopyright", "Copyright (C) 2014-2026"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.7.6.0" VALUE "ProductVersion", "6.7.7.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"
@@ -0,0 +1,525 @@
/*
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 "api/api_stickers_creator.h"
#include "apiwrap.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_stickers_set.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "menu/menu_action_with_thumbnail.h"
#include "storage/file_upload.h"
#include "storage/localimageloader.h"
#include "styles/style_menu_icons.h"
#include "styles/style_widgets.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_common.h"
#include "ui/widgets/popup_menu.h"
namespace Api {
namespace {
constexpr auto kStickerSide = 512;
[[nodiscard]] MTPInputStickerSetItem InputItem(
const MTPInputDocument &document,
const QString &emoji) {
return MTP_inputStickerSetItem(
MTP_flags(0),
document,
MTP_string(emoji),
MTPMaskCoords(),
MTPstring());
}
[[nodiscard]] std::shared_ptr<FilePrepareResult> PrepareStickerWebp(
MTP::DcId dcId,
DocumentId id,
const QByteArray &bytes) {
const auto filename = u"sticker.webp"_q;
auto attributes = QVector<MTPDocumentAttribute>(
1,
MTP_documentAttributeFilename(MTP_string(filename)));
attributes.push_back(MTP_documentAttributeImageSize(
MTP_int(kStickerSide),
MTP_int(kStickerSide)));
auto result = MakePreparedFile({
.id = id,
.type = SendMediaType::File,
});
result->filename = filename;
result->filemime = u"image/webp"_q;
result->content = bytes;
result->filesize = bytes.size();
result->setFileData(bytes);
result->document = MTP_document(
MTP_flags(0),
MTP_long(id),
MTP_long(0),
MTP_bytes(),
MTP_int(base::unixtime::now()),
MTP_string("image/webp"),
MTP_long(bytes.size()),
MTP_vector<MTPPhotoSize>(),
MTPVector<MTPVideoSize>(),
MTP_int(dcId),
MTP_vector<MTPDocumentAttribute>(std::move(attributes)));
return result;
}
void FeedSetIfFull(
not_null<Main::Session*> session,
const MTPmessages_StickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &data) {
session->data().stickers().feedSetFull(data);
session->data().stickers().notifyUpdated(
Data::StickersType::Stickers);
}, [](const auto &) {
});
}
template <typename Callback>
void EnumerateOwnedSets(
not_null<Main::Session*> session,
Data::StickersType type,
Callback &&callback) {
const auto &stickers = session->data().stickers();
const auto &sets = stickers.sets();
const auto &order = (type == Data::StickersType::Emoji)
? stickers.emojiSetsOrder()
: stickers.setsOrder();
for (const auto setId : order) {
const auto it = sets.find(setId);
if (it == sets.end()) {
continue;
}
const auto set = it->second.get();
if (!(set->flags & Data::StickersSetFlag::AmCreator)
|| (set->type() != type)) {
continue;
}
using namespace Data;
if constexpr (std::is_same_v<
bool,
std::invoke_result_t<Callback, not_null<StickersSet*>>>) {
if (!callback(set)) {
return;
}
} else {
callback(set);
}
}
}
void FillChooseOwnedSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document,
Data::StickersType type) {
const auto session = &show->session();
const auto emoji = StickerEmojiOrDefault(document);
const auto isEmoji = (type == Data::StickersType::Emoji);
const auto maxCount = isEmoji
? kEmojiInOwnedSetMax
: kStickersInOwnedSetMax;
const auto fullMessage = isEmoji
? tr::lng_emoji_set_is_full
: tr::lng_stickers_set_is_full;
const auto addedMessage = isEmoji
? tr::lng_emoji_added
: tr::lng_stickers_create_added;
const auto alreadyMessage = isEmoji
? tr::lng_emoji_already_in_set
: tr::lng_stickers_already_in_set;
const auto failToast = [=](QString err) {
show->showToast(err.isEmpty()
? tr::lng_attach_failed(tr::now)
: err);
};
EnumerateOwnedSets(session, type, [&](not_null<Data::StickersSet*> set) {
const auto identifier = set->identifier();
const auto coverDocument = set->lookupThumbnailDocument();
auto thumbnail = coverDocument
? Ui::MakeDocumentThumbnail(
coverDocument,
Data::FileOriginStickerSet(set->id, set->accessHash))
: nullptr;
const auto targetSetId = set->id;
const auto handler = crl::guard(session, [=] {
const auto &map = session->data().stickers().sets();
const auto i = map.find(targetSetId);
if (i != map.end() && i->second->count >= maxCount) {
show->showToast(fullMessage(tr::now));
return;
}
const auto oldCount = (i != map.end())
? i->second->count
: 0;
AddExistingStickerToSet(
session,
identifier,
document,
emoji,
crl::guard(session, [=](MTPmessages_StickerSet) {
const auto &map = session->data().stickers().sets();
const auto i = map.find(targetSetId);
const auto newCount = (i != map.end())
? i->second->count
: oldCount;
show->showToast(newCount > oldCount
? addedMessage(tr::now)
: alreadyMessage(tr::now));
}),
crl::guard(session, failToast));
});
const auto rawAction = Ui::Menu::CreateAction(
menu.get(),
set->title,
handler);
auto item = base::make_unique_q<Menu::ActionWithThumbnail>(
menu->menu(),
menu->menu()->st(),
rawAction,
std::move(thumbnail),
st::menuIconStickerAdd.width());
menu->addAction(std::move(item));
});
}
} // namespace
void AddExistingStickerToSet(
not_null<Main::Session*> session,
const StickerSetIdentifier &set,
not_null<DocumentData*> document,
const QString &emoji,
Fn<void(MTPmessages_StickerSet)> done,
Fn<void(QString)> fail) {
session->api().request(MTPstickers_AddStickerToSet(
Data::InputStickerSet(set),
InputItem(document->mtpInput(), emoji))
).done([=](const MTPmessages_StickerSet &result) {
FeedSetIfFull(session, result);
if (done) {
done(result);
}
}).fail([=](const MTP::Error &error) {
if (fail) {
fail(error.type());
}
}).handleFloodErrors().send();
}
QString StickerEmojiOrDefault(not_null<DocumentData*> document) {
if (const auto sticker = document->sticker()) {
if (!sticker->alt.isEmpty()) {
return sticker->alt;
}
}
return QString::fromUtf8("\xF0\x9F\x99\x82");
}
bool HasOwnedStickerSets(not_null<Main::Session*> session) {
auto found = false;
EnumerateOwnedSets(
session,
Data::StickersType::Stickers,
[&](not_null<Data::StickersSet*>) {
found = true;
return false;
});
return found;
}
bool HasOwnedEmojiSets(not_null<Main::Session*> session) {
auto found = false;
EnumerateOwnedSets(
session,
Data::StickersType::Emoji,
[&](not_null<Data::StickersSet*>) {
found = true;
return false;
});
return found;
}
void FillChooseStickerSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document) {
using namespace Data;
FillChooseOwnedSetMenu(menu, show, document, StickersType::Stickers);
}
void FillChooseEmojiSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document) {
FillChooseOwnedSetMenu(menu, show, document, Data::StickersType::Emoji);
}
void AddAddToStickerSetAction(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document) {
const auto session = &show->session();
if (!HasOwnedStickerSets(session)) {
return;
}
addAction({
.text = tr::lng_stickers_add_to_set(tr::now),
.icon = &st::menuIconStickerAdd,
.fillSubmenu = [show, document](not_null<Ui::PopupMenu*> submenu) {
FillChooseStickerSetMenu(submenu, show, document);
},
.submenuSt = &st::popupMenuWithIcons,
});
}
void AddAddToEmojiSetAction(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document) {
const auto session = &show->session();
if (!HasOwnedEmojiSets(session)) {
return;
}
addAction({
.text = tr::lng_emoji_add_to_set(tr::now),
.icon = &st::menuIconEmoji,
.fillSubmenu = [show, document](not_null<Ui::PopupMenu*> submenu) {
FillChooseEmojiSetMenu(submenu, show, document);
},
.submenuSt = &st::popupMenuWithIcons,
});
}
void DeleteStickerSet(
not_null<Main::Session*> session,
const StickerSetIdentifier &set,
Fn<void()> done,
Fn<void(QString)> fail) {
session->api().request(MTPstickers_DeleteStickerSet(
Data::InputStickerSet(set))
).done([=] {
session->data().stickers().notifyUpdated(
Data::StickersType::Stickers);
if (done) {
done();
}
}).fail([=](const MTP::Error &error) {
if (fail) {
fail(error.type());
}
}).send();
}
StickerUpload::StickerUpload(
not_null<Main::Session*> session,
StickerSetIdentifier set,
QByteArray webpBytes,
QString emoji)
: _session(session)
, _set(std::move(set))
, _bytes(std::move(webpBytes))
, _emoji(std::move(emoji))
, _api(&session->mtp()) {
}
StickerUpload::~StickerUpload() {
cancel();
}
void StickerUpload::start(
Fn<void(MTPmessages_StickerSet)> done,
Fn<void(QString)> fail,
Fn<void(int)> progress) {
Expects(!_uploadId);
_done = std::move(done);
_fail = std::move(fail);
_progress = std::move(progress);
_documentId = base::RandomValue<DocumentId>();
auto ready = PrepareStickerWebp(
_session->mtp().mainDcId(),
_documentId,
_bytes);
_uploadId = FullMsgId(
_session->userPeerId(),
_session->data().nextLocalMessageId());
const auto document = _session->data().document(_documentId);
document->uploadingData = std::make_unique<Data::UploadState>(
document->size > 0 ? document->size : int64(_bytes.size()));
_session->uploader().documentReady(
) | rpl::filter([=](const Storage::UploadedMedia &data) {
return data.fullId == _uploadId;
}) | rpl::on_next([=](const Storage::UploadedMedia &data) {
uploadReady(data.info.file);
}, _uploadLifetime);
_session->uploader().documentFailed(
) | rpl::filter([=](const FullMsgId &id) {
return id == _uploadId;
}) | rpl::on_next([=] {
uploadFailed();
}, _uploadLifetime);
if (_progress) {
_session->uploader().documentProgress(
) | rpl::filter([=](const FullMsgId &id) {
return id == _uploadId;
}) | rpl::on_next([=] {
uploadProgressed();
}, _uploadLifetime);
}
_session->uploader().upload(_uploadId, ready);
}
void StickerUpload::cancel() {
if (_uploadId) {
_session->uploader().cancel(_uploadId);
_uploadId = FullMsgId();
}
if (_addRequestId) {
_api.request(_addRequestId).cancel();
_addRequestId = 0;
}
_uploadLifetime.destroy();
_done = nullptr;
_fail = nullptr;
_progress = nullptr;
}
void StickerUpload::uploadProgressed() {
if (!_progress) {
return;
}
const auto document = _session->data().document(_documentId);
if (!document->uploading() || !document->uploadingData) {
return;
}
const auto size = document->uploadingData->size;
if (size <= 0) {
return;
}
const auto percent = int(
(document->uploadingData->offset * 100) / size);
if (percent != _lastReportedPercent) {
_lastReportedPercent = percent;
_progress(percent);
}
}
void StickerUpload::uploadFailed() {
const auto fail = std::move(_fail);
cancel();
if (fail) {
fail(QString());
}
}
void StickerUpload::uploadReady(const MTPInputFile &file) {
_uploadLifetime.destroy();
_uploadId = FullMsgId();
auto attributes = QVector<MTPDocumentAttribute>();
attributes.push_back(MTP_documentAttributeSticker(
MTP_flags(0),
MTP_string(_emoji),
MTP_inputStickerSetEmpty(),
MTPMaskCoords()));
attributes.push_back(MTP_documentAttributeImageSize(
MTP_int(kStickerSide),
MTP_int(kStickerSide)));
const auto media = MTP_inputMediaUploadedDocument(
MTP_flags(0),
file,
MTPInputFile(),
MTP_string("image/webp"),
MTP_vector<MTPDocumentAttribute>(std::move(attributes)),
MTP_vector<MTPInputDocument>(),
MTPInputPhoto(),
MTP_int(0),
MTP_int(0));
_addRequestId = _api.request(MTPmessages_UploadMedia(
MTP_flags(0),
MTPstring(),
MTP_inputPeerSelf(),
media
)).done(crl::guard(this, [=](const MTPMessageMedia &result) {
_addRequestId = 0;
auto inputDoc = (MTPInputDocument*)(nullptr);
auto storage = MTPInputDocument();
result.match([&](const MTPDmessageMediaDocument &data) {
if (const auto doc = data.vdocument()) {
doc->match([&](const MTPDdocument &d) {
storage = MTP_inputDocument(
d.vid(),
d.vaccess_hash(),
d.vfile_reference());
inputDoc = &storage;
}, [](const auto &) {
});
}
}, [](const auto &) {
});
if (inputDoc) {
requestAddSticker(*inputDoc);
} else if (const auto fail = std::move(_fail)) {
cancel();
fail(QString());
}
})).fail(crl::guard(this, [=](const MTP::Error &error) {
_addRequestId = 0;
const auto fail = std::move(_fail);
const auto type = error.type();
cancel();
if (fail) {
fail(type);
}
})).handleFloodErrors().send();
}
void StickerUpload::requestAddSticker(const MTPInputDocument &document) {
_addRequestId = _api.request(MTPstickers_AddStickerToSet(
Data::InputStickerSet(_set),
InputItem(document, _emoji))
).done(crl::guard(this, [=](const MTPmessages_StickerSet &result) {
_addRequestId = 0;
FeedSetIfFull(_session, result);
const auto done = std::move(_done);
cancel();
if (done) {
done(result);
}
})).fail(crl::guard(this, [=](const MTP::Error &error) {
_addRequestId = 0;
const auto fail = std::move(_fail);
const auto type = error.type();
cancel();
if (fail) {
fail(type);
}
})).handleFloodErrors().send();
}
} // namespace Api
@@ -0,0 +1,115 @@
/*
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/weak_ptr.h"
#include "data/stickers/data_stickers.h"
#include "mtproto/sender.h"
class DocumentData;
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class PopupMenu;
namespace Menu {
struct MenuCallback;
} // namespace Menu
} // namespace Ui
namespace Api {
inline constexpr auto kStickersInOwnedSetMax = 120;
inline constexpr auto kEmojiInOwnedSetMax = 200;
void AddExistingStickerToSet(
not_null<Main::Session*> session,
const StickerSetIdentifier &set,
not_null<DocumentData*> document,
const QString &emoji,
Fn<void(MTPmessages_StickerSet)> done,
Fn<void(QString)> fail);
void DeleteStickerSet(
not_null<Main::Session*> session,
const StickerSetIdentifier &set,
Fn<void()> done,
Fn<void(QString)> fail);
[[nodiscard]] bool HasOwnedStickerSets(not_null<Main::Session*> session);
[[nodiscard]] bool HasOwnedEmojiSets(not_null<Main::Session*> session);
[[nodiscard]] QString StickerEmojiOrDefault(
not_null<DocumentData*> document);
void FillChooseStickerSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
void FillChooseEmojiSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
void AddAddToStickerSetAction(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
void AddAddToEmojiSetAction(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
class StickerUpload final : public base::has_weak_ptr {
public:
StickerUpload(
not_null<Main::Session*> session,
StickerSetIdentifier set,
QByteArray webpBytes,
QString emoji);
~StickerUpload();
void start(
Fn<void(MTPmessages_StickerSet)> done,
Fn<void(QString)> fail,
Fn<void(int /*percent*/)> progress = nullptr);
void cancel();
private:
void uploadReady(const MTPInputFile &file);
void uploadFailed();
void uploadProgressed();
void requestAddSticker(const MTPInputDocument &document);
const not_null<Main::Session*> _session;
StickerSetIdentifier _set;
QByteArray _bytes;
QString _emoji;
MTP::Sender _api;
rpl::lifetime _uploadLifetime;
FullMsgId _uploadId;
DocumentId _documentId = 0;
mtpRequestId _addRequestId = 0;
Fn<void(MTPmessages_StickerSet)> _done;
Fn<void(QString)> _fail;
Fn<void(int)> _progress;
int _lastReportedPercent = -1;
};
} // namespace Api
+1
Ver Arquivo
@@ -59,6 +59,7 @@ boxPhotoTitlePosition: point(28px, 20px);
boxPhotoPadding: margins(28px, 28px, 28px, 18px); boxPhotoPadding: margins(28px, 28px, 28px, 18px);
boxPhotoCompressedSkip: 20px; boxPhotoCompressedSkip: 20px;
boxPhotoCaptionSkip: 8px; boxPhotoCaptionSkip: 8px;
boxPhotoCaptionReplyOverlap: 5px;
defaultChangeUserpicIcon: icon {{ "new_chat_photo", activeButtonFg }}; defaultChangeUserpicIcon: icon {{ "new_chat_photo", activeButtonFg }};
defaultUploadUserpicIcon: icon {{ "upload_chat_photo", msgDateImgFg }}; defaultUploadUserpicIcon: icon {{ "upload_chat_photo", msgDateImgFg }};
+199 -139
Ver Arquivo
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mtproto/facade.h" #include "mtproto/facade.h"
#include "mtproto/proxy_check.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "ui/basic_click_handlers.h" #include "ui/basic_click_handlers.h"
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/dropdown_menu.h" #include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/number_input.h"
#include "ui/widgets/fields/password_input.h" #include "ui/widgets/fields/password_input.h"
@@ -62,22 +64,55 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
using ProxyData = MTP::ProxyData; using ProxyData = MTP::ProxyData;
[[nodiscard]] std::vector<QString> ExtractUrlsSimple(const QString &input) { [[nodiscard]] int ClosestProxyRotationTimeoutSection(int value) {
auto result = 0;
auto bestDistance = 0;
for (auto i = 0; i != int(Core::SettingsProxy::kProxyRotationTimeouts.size()); ++i) {
const auto current = Core::SettingsProxy::kProxyRotationTimeouts[i];
const auto distance = (current > value) ? (current - value) : (value - current);
if ((i == 0) || (distance < bestDistance)) {
result = i;
bestDistance = distance;
}
}
return result;
}
[[nodiscard]] std::vector<QString> ExtractLinkCandidates(const QString &input) {
auto urls = std::vector<QString>(); auto urls = std::vector<QString>();
static auto urlRegex = QRegularExpression(R"((https?:\/\/[^\s]+))"); static const auto urlRegex = QRegularExpression(
R"((?:https?:\/\/[^\s]+|tg:\/\/[^\s]+|(?:www\.)?(?:t\.me|telegram\.me|telegram\.dog)\/[^\s]+))",
QRegularExpression::CaseInsensitiveOption);
auto it = urlRegex.globalMatch(input); auto it = urlRegex.globalMatch(input);
while (it.hasNext()) { while (it.hasNext()) {
urls.push_back(it.next().captured(1)); urls.push_back(it.next().captured(0));
} }
return urls; return urls;
} }
[[nodiscard]] QString ProxyDataToString(const ProxyData &proxy) { [[nodiscard]] bool ProxyDataIsShareable(const ProxyData &proxy) {
using Type = ProxyData::Type; using Type = ProxyData::Type;
return u"tg://"_q return (proxy.type == Type::Socks5)
+ (proxy.type == Type::Socks5 ? "socks" : "proxy") || (proxy.type == Type::Mtproto);
}
[[nodiscard]] QString ProxyDataToQueryPath(const ProxyData &proxy) {
using Type = ProxyData::Type;
const auto path = [&] {
switch (proxy.type) {
case Type::Socks5: return u"socks"_q;
case Type::Mtproto: return u"proxy"_q;
case Type::None:
case Type::Http: return QString();
}
Unexpected("Proxy type in ProxyDataToQueryPath.");
}();
if (path.isEmpty()) {
return QString();
}
return path
+ "?server=" + proxy.host + "&port=" + QString::number(proxy.port) + "?server=" + proxy.host + "&port=" + QString::number(proxy.port)
+ ((proxy.type == Type::Socks5 && !proxy.user.isEmpty()) + ((proxy.type == Type::Socks5 && !proxy.user.isEmpty())
? "&user=" + qthelp::url_encode(proxy.user) : "") ? "&user=" + qthelp::url_encode(proxy.user) : "")
@@ -87,6 +122,20 @@ using ProxyData = MTP::ProxyData;
? "&secret=" + proxy.password : ""); ? "&secret=" + proxy.password : "");
} }
[[nodiscard]] QString ProxyDataToLocalLink(const ProxyData &proxy) {
const auto queryPath = ProxyDataToQueryPath(proxy);
return queryPath.isEmpty() ? QString() : (u"tg://"_q + queryPath);
}
[[nodiscard]] QString ProxyDataToPublicLink(
const Main::Session &session,
const ProxyData &proxy) {
const auto queryPath = ProxyDataToQueryPath(proxy);
return queryPath.isEmpty()
? QString()
: session.createInternalLinkFull(queryPath);
}
[[nodiscard]] ProxyData ProxyDataFromFields( [[nodiscard]] ProxyData ProxyDataFromFields(
ProxyData::Type type, ProxyData::Type type,
const QMap<QString, QString> &fields) { const QMap<QString, QString> &fields) {
@@ -110,7 +159,7 @@ void AddProxyFromClipboard(
const auto socksString = u"socks"_q; const auto socksString = u"socks"_q;
const auto protocol = u"tg://"_q; const auto protocol = u"tg://"_q;
const auto maybeUrls = ExtractUrlsSimple( const auto maybeUrls = ExtractLinkCandidates(
QGuiApplication::clipboard()->text()); QGuiApplication::clipboard()->text());
const auto isSingle = maybeUrls.size() == 1; const auto isSingle = maybeUrls.size() == 1;
@@ -128,8 +177,8 @@ void AddProxyFromClipboard(
protocol.size(), protocol.size(),
8192); 8192);
if (local.startsWith(protocol + proxyString) if (local.startsWith(protocol + proxyString, Qt::CaseInsensitive)
|| local.startsWith(protocol + socksString)) { || local.startsWith(protocol + socksString, Qt::CaseInsensitive)) {
using namespace qthelp; using namespace qthelp;
const auto options = RegExOption::CaseInsensitive; const auto options = RegExOption::CaseInsensitive;
@@ -188,7 +237,10 @@ void AddProxyFromClipboard(
auto success = Result::Failed; auto success = Result::Failed;
for (const auto &maybeUrl : maybeUrls) { for (const auto &maybeUrl : maybeUrls) {
const auto result = proceedUrl(Core::TryConvertUrlToLocal(maybeUrl)); const auto trimmed = maybeUrl.trimmed();
const auto local = Core::TryConvertUrlToLocal(trimmed);
const auto check = local.isEmpty() ? trimmed : local;
const auto result = proceedUrl(check);
if (success != Result::Success) { if (success != Result::Success) {
success = result; success = result;
} }
@@ -376,12 +428,16 @@ private:
void setupButtons(int id, not_null<ProxyRow*> button); void setupButtons(int id, not_null<ProxyRow*> button);
int rowHeight() const; int rowHeight() const;
void refreshProxyForCalls(); void refreshProxyForCalls();
void refreshProxyRotation();
not_null<ProxiesBoxController*> _controller; not_null<ProxiesBoxController*> _controller;
Core::SettingsProxy &_settings; Core::SettingsProxy &_settings;
QPointer<Ui::Checkbox> _tryIPv6; QPointer<Ui::Checkbox> _tryIPv6;
std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings; std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls; QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyRotation;
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _proxyRotationOptions;
QPointer<Ui::SettingsSlider> _proxyRotationTimeout;
QPointer<Ui::DividerLabel> _about; QPointer<Ui::DividerLabel> _about;
base::unique_qptr<Ui::RpWidget> _noRows; base::unique_qptr<Ui::RpWidget> _noRows;
object_ptr<Ui::VerticalLayout> _initialWrap; object_ptr<Ui::VerticalLayout> _initialWrap;
@@ -900,6 +956,47 @@ void ProxiesBox::setupContent() {
0, 0,
st::proxyTryIPv6Padding.right(), st::proxyTryIPv6Padding.right(),
st::proxyTryIPv6Padding.top())); st::proxyTryIPv6Padding.top()));
_proxyRotation = inner->add(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
object_ptr<Ui::Checkbox>(
inner,
tr::lng_proxy_auto_switch(tr::now),
_settings.proxyRotationEnabled()),
style::margins(
0,
st::proxyUsePadding.top(),
0,
st::proxyUsePadding.bottom())),
style::margins(
st::proxyTryIPv6Padding.left(),
0,
st::proxyTryIPv6Padding.right(),
st::proxyTryIPv6Padding.top()));
_proxyRotationOptions = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
_proxyRotationTimeout = _proxyRotationOptions->entity()->add(
object_ptr<Ui::SettingsSlider>(
_proxyRotationOptions->entity(),
st::settingsSlider),
st::settingsBigScalePadding);
for (const auto seconds : Core::SettingsProxy::kProxyRotationTimeouts) {
_proxyRotationTimeout->addSection(
tr::lng_proxy_auto_switch_timeout(
tr::now,
lt_count,
seconds));
}
_proxyRotationTimeout->setActiveSectionFast(
ClosestProxyRotationTimeoutSection(_settings.proxyRotationTimeout()));
_proxyRotationOptions->entity()->add(
object_ptr<Ui::FlatLabel>(
_proxyRotationOptions->entity(),
tr::lng_proxy_auto_switch_about(tr::now),
st::boxDividerLabel),
st::proxyAboutPadding);
_about = inner->add( _about = inner->add(
object_ptr<Ui::DividerLabel>( object_ptr<Ui::DividerLabel>(
@@ -922,6 +1019,7 @@ void ProxiesBox::setupContent() {
addNewProxy(); addNewProxy();
} }
refreshProxyForCalls(); refreshProxyForCalls();
refreshProxyRotation();
}); });
_tryIPv6->checkedChanges( _tryIPv6->checkedChanges(
) | rpl::on_next([=](bool checked) { ) | rpl::on_next([=](bool checked) {
@@ -931,18 +1029,33 @@ void ProxiesBox::setupContent() {
_controller->proxySettingsValue( _controller->proxySettingsValue(
) | rpl::on_next([=](ProxyData::Settings value) { ) | rpl::on_next([=](ProxyData::Settings value) {
_proxySettings->setValue(value); _proxySettings->setValue(value);
refreshProxyForCalls();
refreshProxyRotation();
}, inner->lifetime()); }, inner->lifetime());
_proxyForCalls->entity()->checkedChanges( _proxyForCalls->entity()->checkedChanges(
) | rpl::on_next([=](bool checked) { ) | rpl::on_next([=](bool checked) {
_controller->setProxyForCalls(checked); _controller->setProxyForCalls(checked);
}, _proxyForCalls->lifetime()); }, _proxyForCalls->lifetime());
_proxyRotation->entity()->checkedChanges(
) | rpl::on_next([=](bool checked) {
_controller->setProxyRotationEnabled(checked);
refreshProxyRotation();
}, _proxyRotation->lifetime());
_proxyRotationTimeout->sectionActivated(
) | rpl::on_next([=](int section) {
_controller->setProxyRotationTimeout(
Core::SettingsProxy::kProxyRotationTimeouts[section]);
}, _proxyRotationTimeout->lifetime());
if (_rows.empty()) { if (_rows.empty()) {
createNoRowsLabel(); createNoRowsLabel();
} }
refreshProxyForCalls(); refreshProxyForCalls();
refreshProxyRotation();
_proxyForCalls->finishAnimating(); _proxyForCalls->finishAnimating();
_proxyRotation->finishAnimating();
_proxyRotationOptions->finishAnimating();
{ {
const auto wrap = inner->add( const auto wrap = inner->add(
@@ -987,6 +1100,20 @@ void ProxiesBox::refreshProxyForCalls() {
anim::type::normal); anim::type::normal);
} }
void ProxiesBox::refreshProxyRotation() {
if (!_proxyRotation || !_proxyRotationOptions) {
return;
}
const auto visible = (_proxySettings->current()
== ProxyData::Settings::Enabled)
&& _settings.selected()
&& (_settings.list().size() > 1);
_proxyRotation->toggle(visible, anim::type::normal);
_proxyRotationOptions->toggle(
visible && _proxyRotation->entity()->checked(),
anim::type::normal);
}
int ProxiesBox::rowHeight() const { int ProxiesBox::rowHeight() const {
return st::proxyRowPadding.top() return st::proxyRowPadding.top()
+ st::semiboldFont->height + st::semiboldFont->height
@@ -1029,6 +1156,7 @@ void ProxiesBox::applyView(View &&view) {
} else { } else {
i->second->updateFields(std::move(view)); i->second->updateFields(std::move(view));
} }
refreshProxyRotation();
} }
void ProxiesBox::createNoRowsLabel() { void ProxiesBox::createNoRowsLabel() {
@@ -1359,95 +1487,7 @@ void ProxyBox::addLabel(
} }
using Connection = MTP::details::AbstractConnection; using Connection = MTP::details::AbstractConnection;
using Checker = MTP::details::ConnectionPointer; using Checker = MTP::ProxyCheckConnection;
void ResetProxyCheckers(Checker &v4, Checker &v6) {
v4 = nullptr;
v6 = nullptr;
}
void DropProxyChecker(Checker &v4, Checker &v6, not_null<Connection*> raw) {
if (v4.get() == raw) {
v4 = nullptr;
} else if (v6.get() == raw) {
v6 = nullptr;
}
}
[[nodiscard]] bool HasProxyCheckers(const Checker &v4, const Checker &v6) {
return v4 || v6;
}
void StartProxyCheck(
not_null<MTP::Instance*> mtproto,
const ProxyData &proxy,
Checker &v4,
Checker &v6,
Fn<void(Connection *raw, int ping)> done,
Fn<void(Connection *raw)> fail) {
using Variants = MTP::DcOptions::Variants;
ResetProxyCheckers(v4, v6);
const auto connType = (proxy.type == ProxyData::Type::Http)
? Variants::Http
: Variants::Tcp;
const auto dcId = mtproto->mainDcId();
const auto setup = [&](Checker &checker, const bytes::vector &secret) {
checker = Connection::Create(
mtproto,
connType,
QThread::currentThread(),
secret,
proxy);
const auto raw = checker.get();
raw->connect(raw, &Connection::connected, [=] {
if (done) {
done(raw, raw->pingTime());
}
});
const auto failed = [=] {
if (fail) {
fail(raw);
}
};
raw->connect(raw, &Connection::disconnected, failed);
raw->connect(raw, &Connection::error, failed);
};
if (proxy.type == ProxyData::Type::Mtproto) {
const auto secret = proxy.secretFromMtprotoPassword();
setup(v4, secret);
v4->connectToServer(
proxy.host,
proxy.port,
secret,
dcId,
false);
return;
}
const auto options = mtproto->dcOptions().lookup(
dcId,
MTP::DcType::Regular,
true);
const auto tryConnect = [&](Checker &checker, Variants::Address address) {
const auto &list = options.data[address][connType];
if (list.empty()
|| ((address == Variants::IPv6)
&& !Core::App().settings().proxy().tryIPv6())) {
checker = nullptr;
return;
}
const auto &endpoint = list.front();
setup(checker, endpoint.secret);
checker->connectToServer(
QString::fromStdString(endpoint.ip),
endpoint.port,
endpoint.secret,
dcId,
false);
};
tryConnect(v4, Variants::IPv4);
tryConnect(v6, Variants::IPv6);
}
} // namespace } // namespace
@@ -1609,18 +1649,19 @@ void ProxiesBoxController::ShowApplyConfirmation(
}; };
statusLabel->setTextColorOverride(st::proxyRowStatusFg->c); statusLabel->setTextColorOverride(st::proxyRowStatusFg->c);
relayout(); relayout();
StartProxyCheck( MTP::StartProxyCheck(
&account->mtp(), &account->mtp(),
proxy, proxy,
Core::App().settings().proxy().tryIPv6(),
state->v4, state->v4,
state->v6, state->v6,
[=](Connection *raw, int ping) { [=](Connection *raw, int ping) {
if (!weak || state->finished) { if (!weak || state->finished) {
return; return;
} }
DropProxyChecker(state->v4, state->v6, raw); MTP::DropProxyChecker(state->v4, state->v6, raw);
state->finished = true; state->finished = true;
ResetProxyCheckers(state->v4, state->v6); MTP::ResetProxyCheckers(state->v4, state->v6);
state->statusValue = TextWithEntities{ state->statusValue = TextWithEntities{
tr::lng_proxy_box_table_available( tr::lng_proxy_box_table_available(
tr::now, tr::now,
@@ -1635,13 +1676,13 @@ void ProxiesBoxController::ShowApplyConfirmation(
if (!weak || state->finished) { if (!weak || state->finished) {
return; return;
} }
DropProxyChecker(state->v4, state->v6, raw); MTP::DropProxyChecker(state->v4, state->v6, raw);
if (!HasProxyCheckers(state->v4, state->v6)) { if (!MTP::HasProxyCheckers(state->v4, state->v6)) {
state->finished = true; state->finished = true;
setUnavailable(); setUnavailable();
} }
}); });
if (!HasProxyCheckers(state->v4, state->v6)) { if (!MTP::HasProxyCheckers(state->v4, state->v6)) {
state->finished = true; state->finished = true;
setUnavailable(); setUnavailable();
} }
@@ -1681,9 +1722,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
const auto enableButton = box->addButton( const auto enableButton = box->addButton(
tr::lng_proxy_box_table_button(), tr::lng_proxy_box_table_button(),
[=] { [=] {
auto &proxies = Core::App().settings().proxy().list(); auto &settings = Core::App().settings().proxy();
if (!ranges::contains(proxies, proxy)) { if (settings.indexInList(proxy) < 0) {
proxies.push_back(proxy); settings.addToList(proxy);
} }
Core::App().setCurrentProxy( Core::App().setCurrentProxy(
proxy, proxy,
@@ -1719,9 +1760,10 @@ auto ProxiesBoxController::proxySettingsValue() const
void ProxiesBoxController::refreshChecker(Item &item) { void ProxiesBoxController::refreshChecker(Item &item) {
item.state = ItemState::Checking; item.state = ItemState::Checking;
const auto id = item.id; const auto id = item.id;
StartProxyCheck( MTP::StartProxyCheck(
&_account->mtp(), &_account->mtp(),
item.data, item.data,
Core::App().settings().proxy().tryIPv6(),
item.checker, item.checker,
item.checkerv6, item.checkerv6,
[=](Connection *raw, int pingTime) { [=](Connection *raw, int pingTime) {
@@ -1732,8 +1774,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
if (item == end(_list)) { if (item == end(_list)) {
return; return;
} }
DropProxyChecker(item->checker, item->checkerv6, raw); MTP::DropProxyChecker(item->checker, item->checkerv6, raw);
ResetProxyCheckers(item->checker, item->checkerv6); MTP::ResetProxyCheckers(item->checker, item->checkerv6);
if (item->state == ItemState::Checking) { if (item->state == ItemState::Checking) {
item->state = ItemState::Available; item->state = ItemState::Available;
item->ping = pingTime; item->ping = pingTime;
@@ -1748,14 +1790,14 @@ void ProxiesBoxController::refreshChecker(Item &item) {
if (item == end(_list)) { if (item == end(_list)) {
return; return;
} }
DropProxyChecker(item->checker, item->checkerv6, raw); MTP::DropProxyChecker(item->checker, item->checkerv6, raw);
if (!HasProxyCheckers(item->checker, item->checkerv6) if (!MTP::HasProxyCheckers(item->checker, item->checkerv6)
&& item->state == ItemState::Checking) { && item->state == ItemState::Checking) {
item->state = ItemState::Unavailable; item->state = ItemState::Unavailable;
updateView(*item); updateView(*item);
} }
}); });
if (!HasProxyCheckers(item.checker, item.checkerv6)) { if (!MTP::HasProxyCheckers(item.checker, item.checkerv6)) {
item.state = ItemState::Unavailable; item.state = ItemState::Unavailable;
} }
} }
@@ -1817,8 +1859,9 @@ void ProxiesBoxController::shareItem(int id, bool qr) {
void ProxiesBoxController::shareItems() { void ProxiesBoxController::shareItems() {
auto result = QString(); auto result = QString();
for (const auto &item : _list) { for (const auto &item : _list) {
if (!item.deleted) { if (!item.deleted && ProxyDataIsShareable(item.data)) {
result += ProxyDataToString(item.data) + '\n' + '\n'; result += ProxyDataToPublicLink(_account->session(), item.data)
+ '\n' + '\n';
} }
} }
if (result.isEmpty()) { if (result.isEmpty()) {
@@ -1854,8 +1897,8 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
item->deleted = deleted; item->deleted = deleted;
if (deleted) { if (deleted) {
auto &proxies = _settings.list(); const auto removed = _settings.removeFromList(item->data);
proxies.erase(ranges::remove(proxies, item->data), end(proxies)); Assert(removed);
if (item->data == _settings.selected()) { if (item->data == _settings.selected()) {
_lastSelectedProxy = _settings.selected(); _lastSelectedProxy = _settings.selected();
@@ -1871,16 +1914,19 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
} }
} }
} else { } else {
auto &proxies = _settings.list(); if (_settings.indexInList(item->data) < 0) {
if (ranges::find(proxies, item->data) == end(proxies)) { const auto &proxies = _settings.list();
auto insertBefore = item + 1; auto insertBefore = item + 1;
while (insertBefore != end(_list) && insertBefore->deleted) { while (insertBefore != end(_list) && insertBefore->deleted) {
++insertBefore; ++insertBefore;
} }
auto insertBeforeIt = (insertBefore == end(_list)) const auto foundIndex = (insertBefore == end(_list))
? end(proxies) ? int(proxies.size())
: ranges::find(proxies, insertBefore->data); : _settings.indexInList(insertBefore->data);
proxies.insert(insertBeforeIt, item->data); const auto insertIndex = (foundIndex >= 0)
? foundIndex
: int(proxies.size());
_settings.insertToList(insertIndex, item->data);
} }
if (!_settings.selected() && _lastSelectedProxy == item->data) { if (!_settings.selected() && _lastSelectedProxy == item->data) {
@@ -1919,8 +1965,8 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
void ProxiesBoxController::replaceItemWith( void ProxiesBoxController::replaceItemWith(
std::vector<Item>::iterator which, std::vector<Item>::iterator which,
std::vector<Item>::iterator with) { std::vector<Item>::iterator with) {
auto &proxies = _settings.list(); const auto removed = _settings.removeFromList(which->data);
proxies.erase(ranges::remove(proxies, which->data), end(proxies)); Assert(removed);
_views.fire({ which->id }); _views.fire({ which->id });
_list.erase(which); _list.erase(which);
@@ -1939,10 +1985,8 @@ void ProxiesBoxController::replaceItemValue(
restoreItem(which->id); restoreItem(which->id);
} }
auto &proxies = _settings.list(); const auto replaced = _settings.replaceInList(which->data, proxy);
const auto i = ranges::find(proxies, which->data); Assert(replaced);
Assert(i != end(proxies));
*i = proxy;
which->data = proxy; which->data = proxy;
refreshChecker(*which); refreshChecker(*which);
@@ -1978,8 +2022,7 @@ bool ProxiesBoxController::contains(const ProxyData &proxy) const {
} }
void ProxiesBoxController::addNewItem(const ProxyData &proxy) { void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
auto &proxies = _settings.list(); _settings.addToList(proxy);
proxies.push_back(proxy);
_list.push_back({ ++_idCounter, proxy }); _list.push_back({ ++_idCounter, proxy });
refreshChecker(_list.back()); refreshChecker(_list.back());
@@ -2016,6 +2059,22 @@ void ProxiesBoxController::setProxyForCalls(bool enabled) {
saveDelayed(); saveDelayed();
} }
void ProxiesBoxController::setProxyRotationEnabled(bool enabled) {
if (_settings.proxyRotationEnabled() == enabled) {
return;
}
_settings.setProxyRotationEnabled(enabled);
saveDelayed();
}
void ProxiesBoxController::setProxyRotationTimeout(int value) {
if (_settings.proxyRotationTimeout() == value) {
return;
}
_settings.setProxyRotationTimeout(value);
saveDelayed();
}
void ProxiesBoxController::setTryIPv6(bool enabled) { void ProxiesBoxController::setTryIPv6(bool enabled) {
if (Core::App().settings().proxy().tryIPv6() == enabled) { if (Core::App().settings().proxy().tryIPv6() == enabled) {
return; return;
@@ -2027,6 +2086,7 @@ void ProxiesBoxController::setTryIPv6(bool enabled) {
} }
void ProxiesBoxController::saveDelayed() { void ProxiesBoxController::saveDelayed() {
Core::App().proxyRotationSettingsChanged();
_saveTimer.callOnce(kSaveSettingsDelayedTimeout); _saveTimer.callOnce(kSaveSettingsDelayedTimeout);
} }
@@ -2037,7 +2097,7 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
rpl::producer<bool> ProxiesBoxController::listShareableChanges() const { rpl::producer<bool> ProxiesBoxController::listShareableChanges() const {
return _views.events_starting_with(ItemView()) | rpl::map([=] { return _views.events_starting_with(ItemView()) | rpl::map([=] {
for (const auto &item : _list) { for (const auto &item : _list) {
if (!item.deleted) { if (!item.deleted && ProxyDataIsShareable(item.data)) {
return true; return true;
} }
} }
@@ -2064,8 +2124,7 @@ void ProxiesBoxController::updateView(const Item &item) {
} }
return ItemState::Connecting; return ItemState::Connecting;
}(); }();
const auto supportsShare = (item.data.type == Type::Socks5) const auto supportsShare = ProxyDataIsShareable(item.data);
|| (item.data.type == Type::Mtproto);
const auto supportsCalls = item.data.supportsCalls(); const auto supportsCalls = item.data.supportsCalls();
_views.fire({ _views.fire({
item.id, item.id,
@@ -2082,18 +2141,19 @@ void ProxiesBoxController::updateView(const Item &item) {
} }
void ProxiesBoxController::share(const ProxyData &proxy, bool qr) { void ProxiesBoxController::share(const ProxyData &proxy, bool qr) {
if (proxy.type == Type::Http) { if (!ProxyDataIsShareable(proxy)) {
return; return;
} }
const auto link = ProxyDataToString(proxy); const auto qrLink = ProxyDataToLocalLink(proxy);
const auto shareLink = ProxyDataToPublicLink(_account->session(), proxy);
if (qr) { if (qr) {
_show->showBox(Box([=](not_null<Ui::GenericBox*> box) { _show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
Ui::FillPeerQrBox(box, nullptr, link, rpl::single(QString())); Ui::FillPeerQrBox(box, nullptr, qrLink, rpl::single(QString()));
box->setTitle(tr::lng_proxy_edit_share_qr_box_title()); box->setTitle(tr::lng_proxy_edit_share_qr_box_title());
})); }));
return; return;
} }
QGuiApplication::clipboard()->setText(link); QGuiApplication::clipboard()->setText(shareLink);
_show->showToast(tr::lng_username_copied(tr::now)); _show->showToast(tr::lng_username_copied(tr::now));
} }
@@ -85,6 +85,8 @@ public:
object_ptr<Ui::BoxContent> addNewItemBox(); object_ptr<Ui::BoxContent> addNewItemBox();
bool setProxySettings(ProxyData::Settings value); bool setProxySettings(ProxyData::Settings value);
void setProxyForCalls(bool enabled); void setProxyForCalls(bool enabled);
void setProxyRotationEnabled(bool enabled);
void setProxyRotationTimeout(int value);
void setTryIPv6(bool enabled); void setTryIPv6(bool enabled);
rpl::producer<ProxyData::Settings> proxySettingsValue() const; rpl::producer<ProxyData::Settings> proxySettingsValue() const;
@@ -53,7 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kMaxOptionsCount = TodoListData::kMaxOptions;
constexpr auto kWarnTitleLimit = 12; constexpr auto kWarnTitleLimit = 12;
constexpr auto kWarnTaskLimit = 24; constexpr auto kWarnTaskLimit = 24;
constexpr auto kErrorLimit = 99; constexpr auto kErrorLimit = 99;
+248 -32
Ver Arquivo
@@ -7,52 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/language_box.h" #include "boxes/language_box.h"
#include "data/data_peer_values.h" #include "base/platform/base_platform_info.h"
#include "lang/lang_keys.h"
#include "ui/boxes/choose_language_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/text/text_entity.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/ripple_animation.h"
#include "ui/toast/toast.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "storage/localstorage.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/translate_box.h" #include "boxes/translate_box.h"
#include "ui/boxes/confirm_box.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "core/application.h" #include "core/application.h"
#include "base/platform/base_platform_info.h" #include "data/data_peer_values.h"
#include "lang/lang_instance.h"
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "platform/platform_translate_provider.h" #include "platform/platform_translate_provider.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "spellcheck/spellcheck_types.h" #include "spellcheck/spellcheck_types.h"
#include "storage/localstorage.h"
#include "ui/accessible/ui_accessible_item.h"
#include "ui/boxes/choose_language_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_entity.h"
#include "ui/text/text_options.h"
#include "ui/toast/toast.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/painter.h"
#include "ui/screen_reader_mode.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_layers.h" #include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_passport.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include "styles/style_passport.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
#include <QtGui/QGuiApplication>
namespace { namespace {
@@ -71,11 +74,13 @@ public:
int count() const; int count() const;
int selected() const; int selected() const;
int chosenIndex() const;
void setSelected(int selected); void setSelected(int selected);
rpl::producer<bool> hasSelection() const; rpl::producer<bool> hasSelection() const;
rpl::producer<bool> isEmpty() const; rpl::producer<bool> isEmpty() const;
void activateSelected(); void activateSelected();
void selectSkip(int dir);
rpl::producer<Language> activations() const; rpl::producer<Language> activations() const;
void changeChosen(const QString &chosen); void changeChosen(const QString &chosen);
@@ -83,10 +88,24 @@ public:
static int DefaultRowHeight(); static int DefaultRowHeight();
QAccessible::Role accessibilityRole() override;
Qt::FocusPolicy accessibilityFocusPolicy() override;
QAccessible::Role accessibilityChildRole() const override;
QAccessible::State accessibilityChildState(int index) const override;
int accessibilityChildCount() const override;
QString accessibilityChildName(int index) const override;
QRect accessibilityChildRect(int index) const override;
int accessibilityChildColumnCount(int row) const override;
QAccessible::Role accessibilityChildSubItemRole() const override;
QString accessibilityChildSubItemName(int row, int column) const override;
QString accessibilityChildSubItemValue(int row, int column) const override;
protected: protected:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
void focusInEvent(QFocusEvent *e) override;
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
@@ -154,6 +173,13 @@ private:
void repaintChecked(not_null<const Row*> row); void repaintChecked(not_null<const Row*> row);
void activateByIndex(int index); void activateByIndex(int index);
enum class Announce {
No,
OnChange,
Always,
};
void setSelected(int index, Announce announce);
void showMenu(int index); void showMenu(int index);
void setForceRippled(not_null<Row*> row, bool rippled); void setForceRippled(not_null<Row*> row, bool rippled);
bool canShare(not_null<const Row*> row) const; bool canShare(not_null<const Row*> row) const;
@@ -183,6 +209,26 @@ private:
}; };
[[nodiscard]] bool ForwardListNavigation(
not_null<QKeyEvent*> e,
not_null<Rows*> rows,
int pageHeight) {
const auto key = e->key();
if (key == Qt::Key_Down) {
rows->selectSkip(1);
} else if (key == Qt::Key_Up) {
rows->selectSkip(-1);
} else if (key == Qt::Key_PageDown || key == Qt::Key_PageUp) {
const auto perPage = std::max(
pageHeight / Rows::DefaultRowHeight(),
1);
rows->selectSkip((key == Qt::Key_PageDown) ? perPage : -perPage);
} else {
return false;
}
return true;
}
class Content : public Ui::RpWidget { class Content : public Ui::RpWidget {
public: public:
Content( Content(
@@ -288,6 +334,44 @@ Rows::Rows(
resizeToWidth(width()); resizeToWidth(width());
setAttribute(Qt::WA_MouseTracking); setAttribute(Qt::WA_MouseTracking);
update(); update();
setAccessibleName(tr::lng_languages(tr::now));
}
void Rows::focusInEvent(QFocusEvent *e) {
if (selected() < 0 && count() > 0) {
const auto chosen = chosenIndex();
setSelected(chosen >= 0 ? chosen : 0, Announce::No);
}
RpWidget::focusInEvent(e);
const auto index = selected();
if (index >= 0) {
InvokeQueued(this, [=] {
if (selected() == index && hasFocus()) {
accessibilityChildFocused(index);
}
});
}
}
void Rows::keyPressEvent(QKeyEvent *e) {
const auto pageHeight = window() ? window()->height() : height();
if (ForwardListNavigation(e, this, pageHeight)) {
return;
}
const auto key = e->key();
if (key == Qt::Key_Home && count() > 0) {
setSelected(0, Announce::Always);
} else if (key == Qt::Key_End && count() > 0) {
setSelected(count() - 1, Announce::Always);
} else if (!e->isAutoRepeat()
&& (key == Qt::Key_Space
|| key == Qt::Key_Return
|| key == Qt::Key_Enter)) {
activateSelected();
} else {
RpWidget::keyPressEvent(e);
}
} }
void Rows::mouseMoveEvent(QMouseEvent *e) { void Rows::mouseMoveEvent(QMouseEvent *e) {
@@ -555,7 +639,10 @@ void Rows::setForceRippled(not_null<Row*> row, bool rippled) {
} }
void Rows::activateByIndex(int index) { void Rows::activateByIndex(int index) {
_chosen = rowByIndex(index).data.id;
_activations.fire_copy(rowByIndex(index).data); _activations.fire_copy(rowByIndex(index).data);
accessibilityChildStateChanged(index, { .checked = true });
accessibilityChildNameChanged(index);
} }
void Rows::leaveEventHook(QEvent *e) { void Rows::leaveEventHook(QEvent *e) {
@@ -631,6 +718,15 @@ int Rows::selected() const {
return indexFromSelection(_selected); return indexFromSelection(_selected);
} }
int Rows::chosenIndex() const {
for (auto i = 0, n = count(); i < n; ++i) {
if (rowByIndex(i).data.id == _chosen) {
return i;
}
}
return -1;
}
void Rows::activateSelected() { void Rows::activateSelected() {
const auto index = selected(); const auto index = selected();
if (index >= 0) { if (index >= 0) {
@@ -638,24 +734,66 @@ void Rows::activateSelected() {
} }
} }
void Rows::selectSkip(int dir) {
const auto limit = count();
auto now = selected();
if (now < 0) {
now = chosenIndex();
}
if (now >= 0) {
const auto changed = now + dir;
if (changed < 0) {
setSelected(0, Announce::Always);
} else if (changed >= limit) {
setSelected(limit - 1, Announce::Always);
} else {
setSelected(changed, Announce::Always);
}
} else if (dir > 0) {
setSelected(0, Announce::Always);
}
}
rpl::producer<Language> Rows::activations() const { rpl::producer<Language> Rows::activations() const {
return _activations.events(); return _activations.events();
} }
void Rows::changeChosen(const QString &chosen) { void Rows::changeChosen(const QString &chosen) {
const auto oldIndex = chosenIndex();
_chosen = chosen;
for (const auto &row : _rows) { for (const auto &row : _rows) {
row.check->setChecked(row.data.id == chosen, anim::type::normal); row.check->setChecked(row.data.id == chosen, anim::type::normal);
} }
const auto newIndex = chosenIndex();
if (newIndex != oldIndex && newIndex >= 0) {
accessibilityChildStateChanged(newIndex, { .checked = true });
accessibilityChildNameChanged(newIndex);
}
} }
void Rows::setSelected(int selected) { void Rows::setSelected(int selected) {
setSelected(selected, Announce::OnChange);
}
void Rows::setSelected(int selected, Announce announce) {
_mouseSelection = false; _mouseSelection = false;
const auto limit = count(); const auto limit = count();
if (selected >= 0 && selected < limit) { const auto clamped = (selected >= 0 && selected < limit)
updateSelected(RowSelection{ selected }); ? selected
: -1;
const auto changed = (indexFromSelection(_selected) != clamped)
|| (clamped < 0 && !v::is_null(_selected));
if (clamped >= 0) {
updateSelected(RowSelection{ clamped });
} else { } else {
updateSelected({}); updateSelected({});
} }
const auto shouldAnnounce = (announce == Announce::Always)
|| (announce == Announce::OnChange && changed);
if (shouldAnnounce && clamped >= 0) {
accessibilityChildNameChanged(clamped);
accessibilityChildFocused(clamped);
}
} }
rpl::producer<bool> Rows::hasSelection() const { rpl::producer<bool> Rows::hasSelection() const {
@@ -875,6 +1013,84 @@ void Rows::paintEvent(QPaintEvent *e) {
} }
} }
QAccessible::Role Rows::accessibilityRole() {
return QAccessible::List;
}
Qt::FocusPolicy Rows::accessibilityFocusPolicy() {
return Qt::TabFocus;
}
QAccessible::Role Rows::accessibilityChildRole() const {
return QAccessible::RadioButton;
}
QAccessible::State Rows::accessibilityChildState(int index) const {
QAccessible::State state;
if (Ui::ScreenReaderModeActive()) {
state.focusable = true;
}
state.checkable = true;
state.checked = (index == chosenIndex());
if (index == selected()) {
state.active = true;
if (hasFocus()) {
state.focused = true;
}
}
return state;
}
int Rows::accessibilityChildCount() const {
return count();
}
QString Rows::accessibilityChildName(int index) const {
if (index < 0 || index >= count()) {
return {};
}
const auto &row = rowByIndex(index);
return row.data.nativeName + u", "_q + row.data.name;
}
QRect Rows::accessibilityChildRect(int index) const {
if (index < 0 || index >= count()) {
return {};
}
const auto &row = rowByIndex(index);
return QRect(0, row.top, width(), row.height);
}
int Rows::accessibilityChildColumnCount(int row) const {
return 2;
}
QAccessible::Role Rows::accessibilityChildSubItemRole() const {
return QAccessible::Cell;
}
QString Rows::accessibilityChildSubItemName(int row, int column) const {
if (column == 0) {
return tr::lng_sr_languages_column_native(tr::now);
} else if (column == 1) {
return tr::lng_sr_languages_column_name(tr::now);
}
return {};
}
QString Rows::accessibilityChildSubItemValue(int row, int column) const {
if (row < 0 || row >= count()) {
return {};
}
const auto &data = rowByIndex(row).data;
if (column == 0) {
return data.nativeName;
} else if (column == 1) {
return data.name;
}
return {};
}
Content::Content( Content::Content(
QWidget *parent, QWidget *parent,
const Languages &recent, const Languages &recent,
+68 -3
Ver Arquivo
@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/send_gif_with_caption_box.h" #include "boxes/send_gif_with_caption_box.h"
#include "boxes/send_credits_box.h" #include "boxes/send_credits_box.h"
#include "boxes/send_files_box_reply_header.h"
#include "ui/effects/scroll_content_shadow.h" #include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
@@ -429,6 +430,10 @@ int SendFilesBox::Block::fromIndex() const {
return _from; return _from;
} }
bool SendFilesBox::Block::isSingleFile() const {
return !_isAlbum && !_isSingleMedia;
}
int SendFilesBox::Block::tillIndex() const { int SendFilesBox::Block::tillIndex() const {
return _till; return _till;
} }
@@ -646,9 +651,58 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
, _inner( , _inner(
_scroll->setOwnedWidget( _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data()))) { object_ptr<Ui::VerticalLayout>(_scroll.data()))) {
setReplyTo(descriptor.replyTo);
enqueueNextPrepare(); enqueueNextPrepare();
} }
void SendFilesBox::setReplyTo(FullReplyTo replyTo) {
if (_replyTo == replyTo) {
return;
} else if (!replyTo.messageId || !replyTo.messageId.peer) {
_replyTo = {};
if (_replyHeader) {
_replyHeader->hideAnimated();
}
return;
}
_replyTo = replyTo;
if (_replyHeader) {
_replyHeader = nullptr;
_replyHeaderHeight = 0;
}
_replyHeader = std::make_unique<SendFiles::ReplyPillHeader>(
this,
_show,
std::move(replyTo));
_replyHeader->setRoundedShapeBelow(
!_blocks.empty() && !_blocks.front().isSingleFile());
_replyHeader->show();
_replyHeader->desiredHeight(
) | rpl::on_next([=](int height) {
if (_replyHeaderHeight.current() != height) {
_replyHeaderHeight = height;
updateBoxSize();
updateControlsGeometry();
}
}, _replyHeader->lifetime());
_replyHeader->closeRequests(
) | rpl::on_next([=] {
_replyTo = {};
if (_replyHeader) {
_replyHeader->hideAnimated();
}
}, _replyHeader->lifetime());
_replyHeader->hideFinished(
) | rpl::on_next([=] {
InvokeQueued(this, [=] {
_replyHeader = nullptr;
_replyHeaderHeight = 0;
updateBoxSize();
updateControlsGeometry();
});
}, _replyHeader->lifetime());
}
Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails( Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
const SendFilesBoxDescriptor &descriptor) { const SendFilesBoxDescriptor &descriptor) {
auto initial = descriptor.sendMenuDetails; auto initial = descriptor.sendMenuDetails;
@@ -1219,6 +1273,10 @@ void SendFilesBox::generatePreviewFrom(int fromBlock) {
if (albumStart >= 0) { if (albumStart >= 0) {
pushBlock(albumStart, _list.files.size()); pushBlock(albumStart, _list.files.size());
} }
if (_replyHeader) {
_replyHeader->setRoundedShapeBelow(
!_blocks.empty() && !_blocks.front().isSingleFile());
}
} }
void SendFilesBox::pushBlock(int from, int till) { void SendFilesBox::pushBlock(int from, int till) {
@@ -2095,6 +2153,7 @@ void SendFilesBox::updateBoxSize() {
if (!_caption->isHidden()) { if (!_caption->isHidden()) {
footerHeight += st::boxPhotoCaptionSkip + _caption->height(); footerHeight += st::boxPhotoCaptionSkip + _caption->height();
} }
footerHeight += _replyHeaderHeight.current();
const auto pairs = std::array<std::pair<RpWidget*, int>, 5>{ { const auto pairs = std::array<std::pair<RpWidget*, int>, 5>{ {
{ _groupFiles.data(), st::boxPhotoCompressedSkip }, { _groupFiles.data(), st::boxPhotoCompressedSkip },
{ _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip }, { _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
@@ -2184,8 +2243,14 @@ void SendFilesBox::updateControlsGeometry() {
bottom -= pair.second + pointer->heightNoMargins(); bottom -= pair.second + pointer->heightNoMargins();
} }
} }
_scroll->resize(width(), bottom - _titleHeight.current()); const auto replyH = _replyHeaderHeight.current();
_scroll->move(0, _titleHeight.current()); const auto replyTopOverlap = std::min(st::boxPhotoCaptionSkip, replyH);
const auto replyTop = _titleHeight.current() - replyTopOverlap;
if (_replyHeader) {
_replyHeader->setGeometry(0, replyTop, width(), replyH);
}
_scroll->resize(width(), bottom - replyTop - replyH);
_scroll->move(0, replyTop + replyH);
} }
void SendFilesBox::showFinished() { void SendFilesBox::showFinished() {
@@ -2333,7 +2398,7 @@ void SendFilesBox::send(
} }
} }
_confirmedCallback(std::move(bundle), options); _confirmedCallback(std::move(bundle), options, _replyTo);
} }
closeBox(); closeBox();
} }
+14 -1
Ver Arquivo
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "base/flags.h" #include "base/flags.h"
#include "data/data_msg_id.h"
#include "ui/layers/box_content.h" #include "ui/layers/box_content.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
@@ -62,6 +63,10 @@ class CharactersLimitLabel;
class ComposeAiButton; class ComposeAiButton;
} // namespace HistoryView::Controls } // namespace HistoryView::Controls
namespace SendFiles {
class ReplyPillHeader;
} // namespace SendFiles
enum class SendFilesAllow { enum class SendFilesAllow {
OnlyOne = (1 << 0), OnlyOne = (1 << 0),
Photos = (1 << 1), Photos = (1 << 1),
@@ -91,7 +96,8 @@ using SendFilesCheck = Fn<bool(
using SendFilesConfirmed = Fn<void( using SendFilesConfirmed = Fn<void(
std::shared_ptr<Ui::PreparedBundle>, std::shared_ptr<Ui::PreparedBundle>,
Api::SendOptions)>; Api::SendOptions,
FullReplyTo)>;
struct SendFilesBoxDescriptor { struct SendFilesBoxDescriptor {
std::shared_ptr<ChatHelpers::Show> show; std::shared_ptr<ChatHelpers::Show> show;
@@ -105,6 +111,7 @@ struct SendFilesBoxDescriptor {
const style::ComposeControls *stOverride = nullptr; const style::ComposeControls *stOverride = nullptr;
SendFilesConfirmed confirmed; SendFilesConfirmed confirmed;
Fn<void()> cancelled; Fn<void()> cancelled;
FullReplyTo replyTo;
}; };
class SendFilesBox : public Ui::BoxContent { class SendFilesBox : public Ui::BoxContent {
@@ -129,6 +136,7 @@ public:
void setCancelledCallback(Fn<void()> callback) { void setCancelledCallback(Fn<void()> callback) {
_cancelledCallback = std::move(callback); _cancelledCallback = std::move(callback);
} }
void setReplyTo(FullReplyTo replyTo);
[[nodiscard]] rpl::producer<TextWithTags> takeTextWithTagsRequests() const; [[nodiscard]] rpl::producer<TextWithTags> takeTextWithTagsRequests() const;
@@ -164,6 +172,7 @@ private:
[[nodiscard]] int fromIndex() const; [[nodiscard]] int fromIndex() const;
[[nodiscard]] int tillIndex() const; [[nodiscard]] int tillIndex() const;
[[nodiscard]] bool isSingleFile() const;
[[nodiscard]] object_ptr<Ui::RpWidget> takeWidget(); [[nodiscard]] object_ptr<Ui::RpWidget> takeWidget();
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const; [[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
@@ -316,6 +325,10 @@ private:
rpl::variable<int> _footerHeight = 0; rpl::variable<int> _footerHeight = 0;
rpl::lifetime _dimensionsLifetime; rpl::lifetime _dimensionsLifetime;
std::unique_ptr<SendFiles::ReplyPillHeader> _replyHeader;
rpl::variable<int> _replyHeaderHeight = 0;
FullReplyTo _replyTo;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
QPointer<Ui::VerticalLayout> _inner; QPointer<Ui::VerticalLayout> _inner;
std::deque<Block> _blocks; std::deque<Block> _blocks;
@@ -0,0 +1,372 @@
/*
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 "boxes/send_files_box_reply_header.h"
#include "chat_helpers/compose/compose_show.h"
#include "core/ui_integration.h"
#include "data/data_changes.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_reply.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/image/image.h"
#include "ui/painter.h"
#include "ui/text/text_options.h"
#include "ui/power_saving.h"
#include "ui/widgets/buttons.h"
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_dialogs.h"
namespace SendFiles {
namespace {
constexpr auto kAnimationDuration = crl::time(180);
} // namespace
ReplyPillHeader::ReplyPillHeader(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
FullReplyTo replyTo)
: RpWidget(parent)
, _show(std::move(show))
, _data(&_show->session().data())
, _replyTo(std::move(replyTo))
, _cancel(Ui::CreateChild<Ui::IconButton>(this, st::sendFilesReplyCancel)) {
resize(
parent->width(),
st::boxPhotoCaptionSkip + st::historyReplyHeight);
_cancel->setAccessibleName(tr::lng_cancel(tr::now));
_cancel->setClickedCallback([=] {
hideAnimated();
});
setShownMessage(_data->message(_replyTo.messageId));
_data->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Edited
| Data::MessageUpdate::Flag::Destroyed
) | rpl::filter([=](const Data::MessageUpdate &update) {
return (update.item == _shownMessage);
}) | rpl::on_next([=](const Data::MessageUpdate &update) {
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
_shownMessage = nullptr;
_shownMessageName.clear();
_shownMessageText.clear();
hideAnimated();
} else {
updateShownMessageText();
RpWidget::update();
}
}, lifetime());
animationCallback();
}
ReplyPillHeader::~ReplyPillHeader() = default;
rpl::producer<> ReplyPillHeader::closeRequests() const {
return _closeRequests.events();
}
rpl::producer<> ReplyPillHeader::hideFinished() const {
return _hideFinished.value()
| rpl::filter(rpl::mappers::_1)
| rpl::to_empty;
}
rpl::producer<int> ReplyPillHeader::desiredHeight() const {
return _desiredHeight.value();
}
void ReplyPillHeader::setRoundedShapeBelow(bool value) {
if (_roundedShapeBelow == value) {
return;
}
_roundedShapeBelow = value;
update();
}
void ReplyPillHeader::hideAnimated() {
if (_hiding) {
return;
}
_hiding = true;
_closeRequests.fire({});
_showAnimation.start(
[=] { animationCallback(); },
1.,
0.,
kAnimationDuration);
}
void ReplyPillHeader::animationCallback() {
const auto full = st::boxPhotoCaptionSkip + st::historyReplyHeight;
const auto value = _showAnimation.value(_hiding ? 0. : 1.);
_desiredHeight = int(base::SafeRound(full * value));
update();
if (_hiding && !_showAnimation.animating()) {
_hideFinished = true;
}
}
void ReplyPillHeader::resolveMessageData() {
const auto id = _replyTo.messageId;
if (!id || !id.peer) {
return;
}
const auto peer = _data->peer(id.peer);
const auto itemId = id.msg;
const auto callback = crl::guard(this, [=] {
if (!_shownMessage) {
if (const auto message = _data->message(peer, itemId)) {
setShownMessage(message);
} else {
hideAnimated();
}
}
});
_data->session().api().requestMessageData(peer, itemId, callback);
}
void ReplyPillHeader::setShownMessage(HistoryItem *item) {
_shownMessage = item;
if (item) {
updateShownMessageText();
const auto context = Core::TextContext({
.session = &item->history()->session(),
.customEmojiLoopLimit = 1,
});
_shownMessageName.setMarkedText(
st::fwdTextStyle,
HistoryView::Reply::ComposePreviewName(
item->history(),
item,
_replyTo),
Ui::NameTextOptions(),
context);
} else {
_shownMessageName.clear();
_shownMessageText.clear();
resolveMessageData();
}
update();
}
void ReplyPillHeader::updateShownMessageText() {
Expects(_shownMessage != nullptr);
const auto context = Core::TextContext({
.session = &_data->session(),
.repaint = [=] { customEmojiRepaint(); },
});
_shownMessageText.setMarkedText(
st::messageTextStyle,
(_replyTo.quote.empty()
? _shownMessage->inReplyText()
: _replyTo.quote),
Ui::DialogTextOptions(),
context);
}
void ReplyPillHeader::customEmojiRepaint() {
if (_repaintScheduled) {
return;
}
_repaintScheduled = true;
update();
}
void ReplyPillHeader::resizeEvent(QResizeEvent *e) {
_cancel->moveToRight(
st::boxPhotoPadding.right() + st::sendBoxAlbumGroupSkipRight,
(st::historyReplyHeight - _cancel->height()) / 2);
}
void ReplyPillHeader::paintEvent(QPaintEvent *e) {
_repaintScheduled = false;
Painter p(this);
p.setInactive(_show->paused(Window::GifPauseReason::Layer));
const auto left = st::boxPhotoPadding.left();
const auto right = st::boxPhotoPadding.right();
const auto bottomSkip = st::boxPhotoCaptionSkip;
const auto pillHeight = height() - bottomSkip;
if (pillHeight <= 0) {
return;
}
const auto pillRect = QRect(
left,
0,
width() - left - right,
pillHeight);
if (pillRect.isEmpty()) {
return;
}
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
const auto topRadius = st::bubbleRadiusLarge;
const auto bottomRadius = _roundedShapeBelow
? st::bubbleRadiusSmall
: st::bubbleRadiusLarge;
const auto rectF = QRectF(pillRect);
auto path = QPainterPath();
path.moveTo(rectF.left() + topRadius, rectF.top());
path.lineTo(rectF.right() - topRadius, rectF.top());
path.arcTo(
rectF.right() - 2 * topRadius,
rectF.top(),
2 * topRadius,
2 * topRadius,
90, -90);
path.lineTo(rectF.right(), rectF.bottom() - bottomRadius);
path.arcTo(
rectF.right() - 2 * bottomRadius,
rectF.bottom() - 2 * bottomRadius,
2 * bottomRadius,
2 * bottomRadius,
0, -90);
path.lineTo(rectF.left() + bottomRadius, rectF.bottom());
path.arcTo(
rectF.left(),
rectF.bottom() - 2 * bottomRadius,
2 * bottomRadius,
2 * bottomRadius,
270, -90);
path.lineTo(rectF.left(), rectF.top() + topRadius);
path.arcTo(
rectF.left(),
rectF.top(),
2 * topRadius,
2 * topRadius,
180, -90);
path.closeSubpath();
p.fillPath(path, st::windowBgOver);
}
const auto iconPos = st::sendFilesReplyIconPosition
+ QPoint(pillRect.left(), pillRect.top());
if (!_replyTo.quote.empty()) {
st::historyQuoteIcon.paint(p, iconPos, width());
} else {
st::historyReplyIcon.paint(p, iconPos, width());
// Remove 'settings' mini-icon.
p.fillRect(
QRect(
QPoint(style::ConvertScale(16), style::ConvertScale(5))
+ iconPos,
QSize(style::ConvertScale(11), style::ConvertScale(8))),
st::windowBgOver);
p.fillRect(
QRect(
QPoint(style::ConvertScale(22), style::ConvertScale(13))
+ iconPos,
QSize(style::ConvertScale(5), style::ConvertScale(2))),
st::windowBgOver);
}
const auto replySkip = st::historyReplySkip;
const auto textLeft = pillRect.left() + replySkip;
const auto availableWidth = _cancel->x() - textLeft;
if (availableWidth <= 0) {
return;
}
const auto pillCenterY = pillRect.top()
+ st::historyReplyHeight / 2;
if (!_shownMessage) {
p.setFont(st::msgDateFont);
p.setPen(st::historyComposeAreaFgService);
const auto top = pillCenterY - st::msgDateFont->height / 2;
p.drawText(
textLeft,
top + st::msgDateFont->ascent,
st::msgDateFont->elided(
tr::lng_profile_loading(tr::now),
availableWidth));
return;
}
const auto media = _shownMessage->media();
const auto hasPreview = media && media->hasReplyPreview();
const auto preview = hasPreview ? media->replyPreview() : nullptr;
const auto spoilered = media && media->hasSpoiler();
if (!spoilered) {
_previewSpoiler = nullptr;
} else if (!_previewSpoiler) {
_previewSpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
update();
});
}
const auto previewSkipValue = st::historyReplyPreview + st::msgReplyBarSkip;
const auto previewSkip = (hasPreview && preview) ? previewSkipValue : 0;
const auto contentLeft = textLeft + previewSkip;
const auto contentAvailable = availableWidth - previewSkip;
if (preview) {
const auto to = QRect(
textLeft,
pillCenterY - st::historyReplyPreview / 2,
st::historyReplyPreview,
st::historyReplyPreview);
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
preview->size() / style::DevicePixelRatio(),
{
.options = Images::Option::RoundSmall,
.outer = to.size(),
}));
if (_previewSpoiler) {
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
_previewSpoiler->index(crl::now(), p.inactive())));
}
}
p.setPen(st::historyReplyNameFg);
p.setFont(st::msgServiceNameFont);
_shownMessageName.drawElided(
p,
contentLeft,
pillRect.top() + st::msgReplyPadding.top(),
contentAvailable);
p.setPen(st::historyComposeAreaFg);
_shownMessageText.draw(p, {
.position = QPoint(
contentLeft,
pillRect.top()
+ st::msgReplyPadding.top()
+ st::msgServiceNameFont->height),
.availableWidth = contentAvailable,
.palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(),
.pausedEmoji = p.inactive() || On(PowerSaving::kEmojiChat),
.pausedSpoiler = p.inactive() || On(PowerSaving::kChatSpoiler),
.elisionLines = 1,
});
}
} // namespace SendFiles
@@ -0,0 +1,77 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
#include "ui/text/text.h"
class HistoryItem;
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
class Session;
} // namespace Data
namespace Ui {
class IconButton;
class SpoilerAnimation;
} // namespace Ui
namespace SendFiles {
class ReplyPillHeader final : public Ui::RpWidget {
public:
ReplyPillHeader(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
FullReplyTo replyTo);
~ReplyPillHeader();
[[nodiscard]] rpl::producer<> closeRequests() const;
[[nodiscard]] rpl::producer<> hideFinished() const;
[[nodiscard]] rpl::producer<int> desiredHeight() const;
void setRoundedShapeBelow(bool value);
void hideAnimated();
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void resolveMessageData();
void setShownMessage(HistoryItem *item);
void updateShownMessageText();
void customEmojiRepaint();
void animationCallback();
const std::shared_ptr<ChatHelpers::Show> _show;
const not_null<Data::Session*> _data;
const FullReplyTo _replyTo;
const not_null<Ui::IconButton*> _cancel;
HistoryItem *_shownMessage = nullptr;
Ui::Text::String _shownMessageName;
Ui::Text::String _shownMessageText;
std::unique_ptr<Ui::SpoilerAnimation> _previewSpoiler;
bool _repaintScheduled = false;
Ui::Animations::Simple _showAnimation;
rpl::variable<int> _desiredHeight = 0;
rpl::event_stream<> _closeRequests;
rpl::variable<bool> _hideFinished = false;
bool _hiding = false;
bool _roundedShapeBelow = true;
};
} // namespace SendFiles
@@ -55,7 +55,6 @@ constexpr auto kSuccessFadeInDuration = crl::time(300);
constexpr auto kSuccessExpandDuration = crl::time(400); constexpr auto kSuccessExpandDuration = crl::time(400);
constexpr auto kSuccessExpandStart = crl::time(100); constexpr auto kSuccessExpandStart = crl::time(100);
constexpr auto kProgressFadeInDuration = crl::time(300); constexpr auto kProgressFadeInDuration = crl::time(300);
constexpr auto kFailureFadeInDuration = crl::time(300);
[[nodiscard]] QString FormatPercent(int permille) { [[nodiscard]] QString FormatPercent(int permille) {
const auto rounded = (permille + 5) / 10; const auto rounded = (permille + 5) / 10;
@@ -0,0 +1,389 @@
/*
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 "boxes/sticker_creator_box.h"
#include "api/api_stickers_creator.h"
#include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/emoji_picker_overlay.h"
#include "core/file_utilities.h"
#include "editor/editor_layer_widget.h"
#include "editor/photo_editor.h"
#include "editor/photo_editor_common.h"
#include "editor/scene/scene.h"
#include "editor/scene/scene_item_image.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/emoji_config.h"
#include "ui/image/image.h"
#include "ui/image/image_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/layers/layer_widget.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_editor.h"
#include "styles/style_layers.h"
#include <QtCore/QBuffer>
#include <QtGui/QImageReader>
namespace {
constexpr auto kStickerSide = 512;
constexpr auto kPreviewSide = 256;
constexpr auto kWebpQuality = 95;
constexpr auto kMaxEmojis = 7;
[[nodiscard]] QImage LoadImageFromFile(const QString &path) {
auto reader = QImageReader(path);
reader.setAutoTransform(true);
auto image = reader.read();
if (image.format() != QImage::Format_ARGB32_Premultiplied
&& image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
return image;
}
class PreviewWidget final : public Ui::RpWidget {
public:
PreviewWidget(QWidget *parent, QImage image)
: RpWidget(parent)
, _image(std::move(image)) {
resize(kPreviewSide, kPreviewSide);
}
protected:
void paintEvent(QPaintEvent *e) override {
auto p = QPainter(this);
auto hq = PainterHighQualityEnabler(p);
const auto target = QRect(0, 0, width(), height());
p.drawImage(target, _image);
}
private:
const QImage _image;
};
void OpenPhotoEditorForSticker(
std::shared_ptr<ChatHelpers::Show> show,
QImage image,
Fn<void(QImage&&)> onDone) {
if (image.isNull()) {
show->showToast(tr::lng_stickers_create_open_failed(tr::now));
return;
}
const auto sessionController = show->resolveWindow();
if (!sessionController) {
show->showToast(tr::lng_stickers_create_open_failed(tr::now));
return;
}
const auto windowController = &sessionController->window();
const auto parentWidget = sessionController->widget();
if (image.width() <= 0
|| image.height() <= 0
|| (image.width() > 10 * image.height())
|| (image.height() > 10 * image.width())) {
show->showToast(tr::lng_stickers_create_open_failed(tr::now));
return;
}
auto canvas = QImage(
kStickerSide,
kStickerSide,
QImage::Format_ARGB32_Premultiplied);
canvas.fill(Qt::transparent);
const auto baseImage = std::make_shared<Image>(std::move(canvas));
auto scene = std::make_shared<Editor::Scene>(
QRectF(0, 0, kStickerSide, kStickerSide));
const auto userPixmap = QPixmap::fromImage(std::move(image));
const auto userSize = userPixmap.size();
const auto fitted = userSize.scaled(
QSize(kStickerSide, kStickerSide),
Qt::KeepAspectRatio);
const auto handle = st::photoEditorItemHandleSize;
const auto itemSize = (userSize.width() >= userSize.height())
? int((fitted.height() + handle)
* userSize.width() / float64(userSize.height()))
: (fitted.width() + handle);
auto itemData = Editor::ItemBase::Data{
.initialZoom = 1.0,
.zPtr = scene->lastZ(),
.size = itemSize,
.x = kStickerSide / 2,
.y = kStickerSide / 2,
.imageSize = userSize,
};
auto imageItem = std::make_shared<Editor::ItemImage>(
QPixmap(userPixmap),
std::move(itemData));
scene->addItem(std::move(imageItem));
auto modifications = Editor::PhotoModifications{
.crop = QRect(0, 0, kStickerSide, kStickerSide),
.paint = std::move(scene),
};
auto editor = base::make_unique_q<Editor::PhotoEditor>(
parentWidget,
windowController,
baseImage,
std::move(modifications),
Editor::EditorData{
.exactSize = QSize(kStickerSide, kStickerSide),
.cropType = Editor::EditorData::CropType::RoundedRect,
.keepAspectRatio = true,
.fixedCrop = true,
});
const auto raw = editor.get();
auto applyModifications = [=, done = std::move(onDone)](
const Editor::PhotoModifications &mods) mutable {
auto result = Editor::ImageModified(baseImage->original(), mods);
if (result.size() != QSize(kStickerSide, kStickerSide)) {
result = result.scaled(
kStickerSide,
kStickerSide,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
done(std::move(result));
};
auto layer = std::make_unique<Editor::LayerWidget>(
parentWidget,
std::move(editor));
Editor::InitEditorLayer(layer.get(), raw, std::move(applyModifications));
windowController->showLayer(
std::move(layer),
Ui::LayerOption::KeepOther);
}
[[nodiscard]] QByteArray EncodeWebp(QImage image) {
if (image.size() != QSize(kStickerSide, kStickerSide)) {
image = image.scaled(
kStickerSide,
kStickerSide,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
auto bytes = QByteArray();
auto buffer = QBuffer(&bytes);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "WEBP", kWebpQuality);
return bytes;
}
} // namespace
namespace Api {
void CreateStickerBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
StickerSetIdentifier set,
QImage image,
Fn<void(MTPmessages_StickerSet)> done) {
struct State {
rpl::variable<bool> uploading = false;
std::unique_ptr<StickerUpload> upload;
QPointer<Ui::RoundButton> addButton;
};
const auto state = box->lifetime().make_state<State>();
const auto session = &show->session();
box->setTitle(tr::lng_stickers_create_image_title());
const auto inner = box->verticalLayout();
auto pickerDescriptor = ChatHelpers::EmojiPickerOverlayDescriptor{
.aboutText = tr::lng_stickers_create_emoji_about(tr::now),
.maxSelected = kMaxEmojis,
.allowExpand = true,
};
const auto metrics = ChatHelpers::EmojiPickerOverlay::EstimateMetrics(
pickerDescriptor.aboutText);
const auto pickerCollapsed = metrics.collapsedHeight;
const auto pickerTotalExpanded = metrics.totalExpandedHeight;
const auto shadowExt = metrics.shadowExtent;
constexpr auto kStickerOverlap = 24;
const auto stickerTop = shadowExt.top()
+ pickerCollapsed
- kStickerOverlap;
const auto holderHeight = std::max(
stickerTop + kPreviewSide,
pickerTotalExpanded);
const auto previewHolder = inner->add(
object_ptr<Ui::RpWidget>(inner),
QMargins(0, 0, 0, 0),
style::al_top);
previewHolder->resize(st::boxWideWidth, holderHeight);
const auto preview = Ui::CreateChild<PreviewWidget>(
previewHolder,
image);
const auto picker = Ui::CreateChild<ChatHelpers::EmojiPickerOverlay>(
previewHolder,
std::move(pickerDescriptor));
auto layoutOverlay = [=] {
const auto bubbleW = std::min(
previewHolder->width()
- 2 * st::boxRowPadding.left()
- shadowExt.left() - shadowExt.right(),
int(kPreviewSide * 1.1));
const auto totalW = bubbleW + shadowExt.left() + shadowExt.right();
const auto x = (previewHolder->width() - totalW) / 2;
picker->setGeometry(x, 0, totalW, pickerTotalExpanded);
picker->raise();
};
previewHolder->widthValue(
) | rpl::on_next([=](int width) {
preview->move((width - kPreviewSide) / 2, stickerTop);
layoutOverlay();
}, preview->lifetime());
Ui::AddSkip(inner);
const auto startUpload = [=, set = std::move(set), done = std::move(done)](
) mutable {
if (state->uploading.current()) {
return;
}
auto emoji = QString();
for (const auto one : picker->selected()) {
emoji.append(one->text());
}
if (emoji.isEmpty()) {
show->showToast(
tr::lng_stickers_create_emoji_required(tr::now));
return;
}
const auto bytes = EncodeWebp(image);
if (bytes.isEmpty()) {
show->showToast(
tr::lng_stickers_create_upload_failed(tr::now));
return;
}
const auto lockedWidth = state->addButton
? state->addButton->width()
: 0;
state->uploading = true;
if (state->addButton && lockedWidth > 0) {
state->addButton->resizeToWidth(lockedWidth);
}
state->upload = std::make_unique<StickerUpload>(
session,
set,
bytes,
emoji);
const auto doneCallback = done;
state->upload->start(
crl::guard(box, [=](MTPmessages_StickerSet result) {
state->upload = nullptr;
state->uploading = false;
show->showToast(tr::lng_stickers_create_added(tr::now));
if (doneCallback) {
doneCallback(result);
}
box->closeBox();
}),
crl::guard(box, [=](QString err) {
state->upload = nullptr;
state->uploading = false;
show->showToast(err.isEmpty()
? tr::lng_stickers_create_upload_failed(tr::now)
: err);
}));
};
const auto addButton = box->addButton(
rpl::conditional(
state->uploading.value(),
rpl::single(QString()),
tr::lng_box_done()),
startUpload);
state->addButton = addButton;
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
addButton,
addButton->height() / 2,
&st::editStickerSetNameLoading);
AddChildToWidgetCenter(addButton, loadingAnimation);
loadingAnimation->showOn(state->uploading.value());
}
box->setWidth(st::boxWideWidth);
box->boxClosing(
) | rpl::on_next([=] {
state->upload = nullptr;
}, box->lifetime());
}
void OpenCreateStickerFlow(
std::shared_ptr<ChatHelpers::Show> show,
StickerSetIdentifier set,
Fn<void(MTPmessages_StickerSet)> done) {
const auto parent = QPointer<QWidget>(show->toastParent());
const auto onChosen = [=, set = std::move(set), done = std::move(done)](
FileDialog::OpenResult &&result) mutable {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
const auto path = result.paths.isEmpty()
? QString()
: result.paths.front();
auto image = path.isEmpty()
? QImage::fromData(result.remoteContent)
: LoadImageFromFile(path);
OpenPhotoEditorForSticker(
show,
std::move(image),
[=, set = std::move(set), done = std::move(done)](
QImage &&prepared) mutable {
show->showBox(Box(
CreateStickerBox,
show,
std::move(set),
std::move(prepared),
std::move(done)));
});
};
FileDialog::GetOpenPath(
parent,
tr::lng_stickers_create_choose_image(tr::now),
FileDialog::ImagesFilter(),
std::move(onChosen));
}
} // namespace Api
@@ -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
#include "data/stickers/data_stickers.h"
#include <QtGui/QImage>
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Api {
void CreateStickerBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
StickerSetIdentifier set,
QImage image,
Fn<void(MTPmessages_StickerSet)> done);
void OpenCreateStickerFlow(
std::shared_ptr<ChatHelpers::Show> show,
StickerSetIdentifier set,
Fn<void(MTPmessages_StickerSet)> done = nullptr);
} // namespace Api
+375 -3
Ver Arquivo
@@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "api/api_common.h" #include "api/api_common.h"
#include "api/api_stickers_creator.h"
#include "api/api_toggling_media.h" #include "api/api_toggling_media.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/sticker_creator_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/stickers_list_widget.h" #include "chat_helpers/stickers_list_widget.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
@@ -52,11 +55,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/gradient_round_button.h" #include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "base/event_filter.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h" #include "styles/style_info.h"
@@ -325,6 +335,7 @@ public:
} }
void applySet(const TLStickerSet &set); void applySet(const TLStickerSet &set);
void setOuterContainer(QPointer<QWidget> container);
~Inner(); ~Inner();
@@ -382,6 +393,16 @@ private:
void startOverAnimation(int index, float64 from, float64 to); void startOverAnimation(int index, float64 from, float64 to);
int stickerFromGlobalPos(const QPoint &p) const; int stickerFromGlobalPos(const QPoint &p) const;
[[nodiscard]] bool hasAddCell() const;
[[nodiscard]] int totalCellsCount() const;
[[nodiscard]] QRect addCellRect() const;
[[nodiscard]] bool addCellFromGlobalPos(const QPoint &p) const;
void setAddCellHovered(bool hovered);
void paintAddCell(QPainter &p) const;
void showAddMenu(QPoint globalPos);
void startAddExistingStickerFlow();
void startCreateNewStickerFlow();
void installDone(const MTPmessages_StickerSetInstallResult &result); void installDone(const MTPmessages_StickerSetInstallResult &result);
void requestReorder(not_null<DocumentData*> document, int index); void requestReorder(not_null<DocumentData*> document, int index);
@@ -465,6 +486,8 @@ private:
mtpRequestId _installRequest = 0; mtpRequestId _installRequest = 0;
int _selected = -1; int _selected = -1;
bool _addCellHovered = false;
bool _addCellPressed = false;
base::Timer _previewTimer; base::Timer _previewTimer;
int _previewShown = -1; int _previewShown = -1;
@@ -472,6 +495,8 @@ private:
bool _previewLocked = false; bool _previewLocked = false;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
base::unique_qptr<ChatHelpers::TabbedPanel> _pickerPanel;
QPointer<QWidget> _outerContainer;
rpl::event_stream<uint64> _setInstalled; rpl::event_stream<uint64> _setInstalled;
rpl::event_stream<uint64> _setArchived; rpl::event_stream<uint64> _setArchived;
@@ -525,6 +550,7 @@ void StickerSetBox::prepare() {
_inner = setInnerWidget( _inner = setInnerWidget(
object_ptr<Inner>(this, _show, _set, _type), object_ptr<Inner>(this, _show, _set, _type),
st::stickersScroll); st::stickersScroll);
_inner->setOuterContainer(getDelegate()->outerContainer());
if (const auto previewId = base::take(_previewDocumentId)) { if (const auto previewId = base::take(_previewDocumentId)) {
_inner->showPreviewForDocument(previewId); _inner->showPreviewForDocument(previewId);
} }
@@ -771,6 +797,98 @@ void StickerSetBox::updateButtons() {
&st::menuIconReorder); &st::menuIconReorder);
}); });
}(); }();
const auto fillSetCreatorFooter = [&] {
using Filler = Fn<void(not_null<Ui::PopupMenu*>)>;
if (!_inner->amSetCreator()) {
return Filler(nullptr);
}
const auto data = &_session->data();
return Filler([=, show = _show, set = _set](
not_null<Ui::PopupMenu*> menu) {
const auto weak = base::weak_qptr<StickerSetBox>(this);
const auto deleteEveryone = [=] {
const auto confirm = [=](Fn<void()> close) {
Api::DeleteStickerSet(
&data->session(),
set,
[=] {
if (const auto strong = weak.get()) {
strong->closeBox();
}
},
[=](const QString &error) {
show->showToast(error);
});
close();
};
show->showBox(Ui::MakeConfirmBox({
.text = tr::lng_stickers_delete_pack_sure(tr::now),
.confirmed = confirm,
.confirmText
= tr::lng_stickers_remove_pack_confirm(),
.confirmStyle = &st::attentionBoxButton,
}));
};
const auto deleteSelf = [show, inner = _inner] {
const auto raw = inner.data();
if (!raw) {
return;
}
auto box = ChatHelpers::MakeConfirmRemoveSetBox(
&show->session(),
st::boxLabel,
raw->setId());
if (box) {
show->showBox(std::move(box));
}
};
const auto deleteAction = menu->addAction(
base::make_unique_q<Ui::Menu::Action>(
menu->menu(),
st::menuWithIconsAttention,
Ui::Menu::CreateAction(
menu->menu().get(),
tr::lng_stickers_context_delete_pack(tr::now),
nullptr),
&st::menuIconDeleteAttention,
&st::menuIconDeleteAttention));
deleteAction->setMenu(
Ui::CreateChild<QMenu>(menu->menu().get()));
const auto sub = menu->ensureSubmenu(
deleteAction,
st::popupMenuWithIcons);
const auto addSub = Ui::Menu::CreateAddActionCallback(sub);
addSub({
.text = tr::lng_stickers_context_delete_pack_everyone(
tr::now),
.handler = deleteEveryone,
.icon = &st::menuIconDeleteAttention,
.isAttention = true,
});
sub->addAction(
tr::lng_stickers_context_delete_pack_self(tr::now),
deleteSelf,
&st::menuIconRemove);
menu->addSeparator(&st::expandedMenuSeparator);
auto item = base::make_unique_q<Ui::Menu::MultilineAction>(
menu->menu(),
st::defaultMenu,
st::historyHasCustomEmoji,
QPoint(
st::defaultMenu.itemPadding.left(),
st::defaultMenu.itemPadding.top()),
tr::lng_stickers_bot_more_options(
tr::now,
lt_bot,
Ui::Text::Colorized(tr::bold(u"@stickers"_q)),
Ui::Text::RichLangValue));
item->clicks(
) | rpl::on_next([] {
UrlClickHandler::Open(u"https://t.me/stickers"_q);
}, item->lifetime());
menu->addAction(std::move(item));
});
}();
if (_inner->notInstalled()) { if (_inner->notInstalled()) {
if (!_session->premium() if (!_session->premium()
&& _session->premiumPossible() && _session->premiumPossible()
@@ -818,6 +936,9 @@ void StickerSetBox::updateButtons() {
: tr::lng_stickers_share_pack)(tr::now), : tr::lng_stickers_share_pack)(tr::now),
[=] { share(); closeBox(); }, [=] { share(); closeBox(); },
&st::menuIconShare); &st::menuIconShare);
if (fillSetCreatorFooter) {
fillSetCreatorFooter(*menu);
}
(*menu)->popup(QCursor::pos()); (*menu)->popup(QCursor::pos());
return true; return true;
}); });
@@ -869,6 +990,9 @@ void StickerSetBox::updateButtons() {
: tr::lng_stickers_archive_pack(tr::now)), : tr::lng_stickers_archive_pack(tr::now)),
archive, archive,
&st::menuIconArchive); &st::menuIconArchive);
if (fillSetCreatorFooter) {
fillSetCreatorFooter(*menu);
}
} }
(*menu)->popup(QCursor::pos()); (*menu)->popup(QCursor::pos());
return true; return true;
@@ -1030,15 +1154,15 @@ void StickerSetBox::Inner::applySet(const TLStickerSet &set) {
_errors.fire(Error::NotFound); _errors.fire(Error::NotFound);
return; return;
} }
_loaded = true;
_perRow = isEmojiSet() ? kEmojiPerRow : kStickersPerRow; _perRow = isEmojiSet() ? kEmojiPerRow : kStickersPerRow;
_rowsCount = (_pack.size() + _perRow - 1) / _perRow;
_singleSize = isEmojiSet() ? st::emojiSetSize : st::stickersSize; _singleSize = isEmojiSet() ? st::emojiSetSize : st::stickersSize;
_rowsCount = (totalCellsCount() + _perRow - 1) / _perRow;
resize( resize(
_padding.left() + _perRow * _singleSize.width(), _padding.left() + _perRow * _singleSize.width(),
_padding.top() + _rowsCount * _singleSize.height() + _padding.bottom()); _padding.top() + _rowsCount * _singleSize.height() + _padding.bottom());
_loaded = true;
if (const auto previewId = base::take(_previewDocumentId)) { if (const auto previewId = base::take(_previewDocumentId)) {
showPreviewForDocument(previewId); showPreviewForDocument(previewId);
} }
@@ -1169,6 +1293,10 @@ void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) { if (e->button() != Qt::LeftButton) {
return; return;
} }
if (addCellFromGlobalPos(e->globalPos())) {
_addCellPressed = !_dragging.enabled;
return;
}
const auto index = stickerFromGlobalPos(e->globalPos()); const auto index = stickerFromGlobalPos(e->globalPos());
if (index < 0 || index >= _pack.size()) { if (index < 0 || index >= _pack.size()) {
return; return;
@@ -1307,6 +1435,7 @@ void StickerSetBox::Inner::showPreviewForDocument(DocumentId documentId) {
void StickerSetBox::Inner::leaveEventHook(QEvent *e) { void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
setSelected(-1); setSelected(-1);
setAddCellHovered(false);
} }
void StickerSetBox::Inner::requestReorder( void StickerSetBox::Inner::requestReorder(
@@ -1399,6 +1528,13 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_previewShown = -1; _previewShown = -1;
return; return;
} }
if (_addCellPressed) {
_addCellPressed = false;
if (addCellFromGlobalPos(e->globalPos())) {
showAddMenu(e->globalPos());
}
return;
}
if (!_previewTimer.isActive()) { if (!_previewTimer.isActive()) {
return; return;
} }
@@ -1470,6 +1606,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
} }
}, &st::menuIconCopy); }, &st::menuIconCopy);
} }
if (!amSetCreator()) {
Api::AddAddToEmojiSetAction(
Ui::Menu::CreateAddActionCallback(_menu.get()),
_show,
_pack[index]);
}
} else if (details.type != SendMenu::Type::Disabled) { } else if (details.type != SendMenu::Type::Disabled) {
const auto document = _pack[index]; const auto document = _pack[index];
const auto send = crl::guard(this, [=](Api::SendOptions options) { const auto send = crl::guard(this, [=](Api::SendOptions options) {
@@ -1501,6 +1643,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
(isFaved (isFaved
? &st::menuIconUnfave ? &st::menuIconUnfave
: &st::menuIconFave)); : &st::menuIconFave));
if (!amSetCreator()) {
Api::AddAddToStickerSetAction(
Ui::Menu::CreateAddActionCallback(_menu.get()),
_show,
document);
}
if (amSetCreator()) { if (amSetCreator()) {
const auto addAction = Ui::Menu::CreateAddActionCallback( const auto addAction = Ui::Menu::CreateAddActionCallback(
_menu.get()); _menu.get());
@@ -1653,8 +1801,27 @@ void StickerSetBox::Inner::fillDeleteStickerBox(
} }
void StickerSetBox::Inner::updateSelected() { void StickerSetBox::Inner::updateSelected() {
auto selected = stickerFromGlobalPos(QCursor::pos()); const auto cursor = QCursor::pos();
const auto onAddCell = addCellFromGlobalPos(cursor);
const auto selected = onAddCell
? -1
: stickerFromGlobalPos(cursor);
setSelected(setType() == Data::StickersType::Masks ? -1 : selected); setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
setAddCellHovered(onAddCell);
}
void StickerSetBox::Inner::setAddCellHovered(bool hovered) {
if (_addCellHovered == hovered) {
return;
}
_addCellHovered = hovered;
if (hasAddCell()) {
setCursor((hovered && !_dragging.enabled)
? style::cur_pointer
: style::cur_default);
const auto rect = addCellRect();
rtlupdate(rect.x(), rect.y(), rect.width(), rect.height());
}
} }
void StickerSetBox::Inner::setSelected(int selected) { void StickerSetBox::Inner::setSelected(int selected) {
@@ -1793,6 +1960,10 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
paintSticker(p, _dragging.index, pos, paused, now); paintSticker(p, _dragging.index, pos, paused, now);
} }
if (hasAddCell()) {
paintAddCell(p);
}
if (_lottiePlayer && !paused) { if (_lottiePlayer && !paused) {
_lottiePlayer->markFrameShown(); _lottiePlayer->markFrameShown();
} }
@@ -2202,4 +2373,205 @@ void StickerSetBox::Inner::repaintItems(crl::time now) {
update(); update();
} }
bool StickerSetBox::Inner::hasAddCell() const {
return _loaded
&& _amSetCreator
&& (setType() == Data::StickersType::Stickers)
&& !_pack.isEmpty()
&& (_pack.size() < Api::kStickersInOwnedSetMax);
}
int StickerSetBox::Inner::totalCellsCount() const {
return _pack.size() + (hasAddCell() ? 1 : 0);
}
QRect StickerSetBox::Inner::addCellRect() const {
const auto index = _pack.size();
const auto row = index / _perRow;
const auto column = index % _perRow;
return QRect(
_padding.left() + column * _singleSize.width(),
_padding.top() + row * _singleSize.height(),
_singleSize.width(),
_singleSize.height());
}
bool StickerSetBox::Inner::addCellFromGlobalPos(const QPoint &p) const {
if (!hasAddCell()) {
return false;
}
auto local = mapFromGlobal(p);
if (rtl()) {
local.setX(width() - local.x());
}
const auto rect = addCellRect();
return rect.contains(local);
}
void StickerSetBox::Inner::paintAddCell(QPainter &p) const {
const auto ltrRect = addCellRect();
const auto rect = rtl()
? QRect(
width() - ltrRect.x() - ltrRect.width(),
ltrRect.y(),
ltrRect.width(),
ltrRect.height())
: ltrRect;
const auto inner = QRect(
rect::center(rect) - QPoint(
st::stickersAddCellBgRadius,
st::stickersAddCellBgRadius),
Size(st::stickersAddCellBgRadius * 2));
auto hq = PainterHighQualityEnabler(p);
const auto base = st::windowSubTextFg->c;
const auto bgAlpha = (_addCellHovered && !_dragging.enabled)
? 0.22
: 0.12;
p.setPen(Qt::NoPen);
p.setBrush(anim::with_alpha(base, bgAlpha));
p.drawEllipse(inner);
const auto plusHalf = st::stickersAddCellPlusSize / 2;
const auto thickness = st::stickersAddCellPlusThickness;
const auto center = rect.center();
const auto plusH = QRectF(
center.x() - plusHalf,
center.y() - thickness / 2.,
plusHalf * 2,
thickness);
const auto plusV = QRectF(
center.x() - thickness / 2.,
center.y() - plusHalf,
thickness,
plusHalf * 2);
const auto radius = thickness / 2.;
p.setBrush(base);
p.drawRoundedRect(plusH, radius, radius);
p.drawRoundedRect(plusV, radius, radius);
}
void StickerSetBox::Inner::showAddMenu(QPoint globalPos) {
if (_dragging.enabled) {
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
_menu->addAction(
tr::lng_stickers_create_new(tr::now),
crl::guard(this, [=] { startCreateNewStickerFlow(); }),
&st::menuIconStickerCreate);
_menu->addAction(
tr::lng_stickers_add_existing(tr::now),
crl::guard(this, [=] { startAddExistingStickerFlow(); }),
&st::menuIconStickerAdd);
_menu->popup(globalPos);
}
void StickerSetBox::Inner::setOuterContainer(QPointer<QWidget> container) {
_outerContainer = std::move(container);
}
void StickerSetBox::Inner::startAddExistingStickerFlow() {
if (!hasAddCell() || !_outerContainer) {
return;
}
const auto container = _outerContainer.data();
const auto identifier = StickerSetIdentifier{
.id = _setId,
.accessHash = _setAccessHash,
.shortName = _setShortName,
};
const auto session = _session;
const auto show = _show;
using Selector = ChatHelpers::TabbedSelector;
_pickerPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
ChatHelpers::TabbedPanelDescriptor{
.ownedSelector = object_ptr<Selector>(
nullptr,
ChatHelpers::TabbedSelectorDescriptor{
.show = _show,
.st = st::defaultComposeControls.tabbed,
.level = Window::GifPauseReason::Layer,
.mode = Selector::Mode::StickersOnly,
.excludeStickerSetId = _setId,
}),
});
const auto panel = _pickerPanel.get();
panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
panel->setDropDown(true);
panel->setShowAnimationOrigin(Ui::PanelAnimation::Origin::TopLeft);
panel->hide();
panel->selector()->fileChosen(
) | rpl::on_next([=, this](const ChatHelpers::FileChosen &chosen) {
const auto document = chosen.document;
if (_pickerPanel) {
_pickerPanel->hideAnimated();
}
const auto emoji = Api::StickerEmojiOrDefault(document);
Api::AddExistingStickerToSet(
session,
identifier,
document,
emoji,
crl::guard(this, [=, this](MTPmessages_StickerSet result) {
applySet(result);
show->showToast(
tr::lng_stickers_create_added(tr::now));
}),
crl::guard(this, [=](QString err) {
show->showToast(err.isEmpty()
? tr::lng_attach_failed(tr::now)
: err);
}));
}, panel->lifetime());
const auto reposition = [=] {
const auto size = container->size();
const auto margins = st::emojiPanMargins;
const auto panelWidth = st::emojiPanWidth
+ margins.left()
+ margins.right();
const auto panelHeight = st::emojiPanMinHeight
+ margins.top()
+ margins.bottom();
const auto top = std::max(0, (size.height() - panelHeight) / 2);
const auto right = (size.width() + panelWidth) / 2;
panel->moveTopRight(top, right);
};
base::install_event_filter(panel, container, [=](
not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
crl::on_main(panel, reposition);
}
return base::EventFilterResult::Continue;
});
reposition();
panel->showAnimated();
}
void StickerSetBox::Inner::startCreateNewStickerFlow() {
if (!hasAddCell()) {
return;
}
const auto identifier = StickerSetIdentifier{
.id = _setId,
.accessHash = _setAccessHash,
.shortName = _setShortName,
};
const auto onDone = crl::guard(this, [=, this](
MTPmessages_StickerSet result) {
applySet(result);
});
Api::OpenCreateStickerFlow(_show, identifier, onDone);
}
StickerSetBox::Inner::~Inner() = default; StickerSetBox::Inner::~Inner() = default;
+1 -2
Ver Arquivo
@@ -52,7 +52,7 @@ callBodyLayout: CallBodyLayout {
participantsTop: 294px; participantsTop: 294px;
muteStroke: 3px; muteStroke: 3px;
muteSize: 36px; muteSize: 36px;
mutePosition: point(142px, 135px); mutePosition: point(140px, 135px);
} }
callBodyWithPreview: CallBodyLayout { callBodyWithPreview: CallBodyLayout {
height: 185px; height: 185px;
@@ -641,7 +641,6 @@ groupCallMenuAbout: FlatLabel(defaultFlatLabel) {
textFg: groupCallMemberNotJoinedStatus; textFg: groupCallMemberNotJoinedStatus;
palette: groupCallTextPalette; palette: groupCallTextPalette;
minWidth: 200px; minWidth: 200px;
maxHeight: 92px;
} }
callDeviceSelectionLabel: FlatLabel(defaultSubsectionTitle) { callDeviceSelectionLabel: FlatLabel(defaultSubsectionTitle) {
textFg: groupCallActiveFg; textFg: groupCallActiveFg;
@@ -66,13 +66,28 @@ AboutItem::AboutItem(
, _dummyAction(new QAction(parent)) { , _dummyAction(new QAction(parent)) {
setPointerCursor(false); setPointerCursor(false);
_text->setSelectable(true);
const auto added = st.itemPadding.left() + st.itemPadding.right();
sizeValue(
) | rpl::on_next([=](const QSize &s) {
if (s.width() <= added) {
return;
}
_text->resizeToWidth(s.width() - added);
_text->moveToLeft(st.itemPadding.left(), st.itemPadding.top());
}, lifetime());
_text->heightValue(
) | rpl::on_next([=] {
resize(width(), contentHeight());
}, lifetime());
_text->resizeToWidth(parent->width() - added);
fitToMenuWidth(); fitToMenuWidth();
enableMouseSelecting(); enableMouseSelecting();
enableMouseSelecting(_text.get()); enableMouseSelecting(_text.get());
_text->setSelectable(true);
_text->resizeToWidth(st::groupCallMenuAbout.minWidth);
_text->moveToLeft(st.itemPadding.left(), st.itemPadding.top());
} }
not_null<QAction*> AboutItem::action() const { not_null<QAction*> AboutItem::action() const {
@@ -37,8 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls::Group { namespace Calls::Group {
namespace { namespace {
constexpr auto kPasswordCharAmount = 24;
void StartWithBox( void StartWithBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
Fn<void()> done, Fn<void()> done,
@@ -424,6 +424,58 @@ stickersScroll: ScrollArea(boxScroll) {
stickersRowDisabledOpacity: 0.4; stickersRowDisabledOpacity: 0.4;
stickersRowDuration: 200; stickersRowDuration: 200;
stickersAddCellPlusSize: 22px;
stickersAddCellPlusThickness: 2px;
stickersAddCellBgRadius: 28px;
stickersEmojiPickerExpandedRadius: 20px;
stickersEmojiPickerBg: emojiPanBg;
stickersEmojiPickerShadow: windowShadowFg;
stickersEmojiPickerPadding: margins(12px, 8px, 12px, 0px);
stickersEmojiPickerItemSize: 30px;
stickersEmojiPickerItemSkip: 4px;
stickersEmojiPickerStripHeight: 40px;
stickersEmojiPickerExpandedHeight: 220px;
stickersEmojiPickerStripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },
{ "chat/reactions_bubble", emojiPanBg },
};
stickersEmojiPickerStripBubbleRight: 20px;
stickersEmojiPickerSelectedBg: windowBgActive;
stickersEmojiPickerSelectedFg: windowBgActive;
stickersEmojiPickerHeaderFg: windowSubTextFg;
stickersEmojiPickerScroll: ScrollArea(boxScroll) {
width: 14px;
deltax: 5px;
deltat: 4px;
deltab: 18px;
}
stickersEmojiPickerAbout: FlatLabel(defaultFlatLabel) {
minWidth: 100px;
align: align(top);
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(12px);
}
}
stickersEmojiPickerSectionHeader: FlatLabel(defaultFlatLabel) {
minWidth: 10px;
align: align(topleft);
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(12px semibold);
}
}
stickersEmojiPickerExpandIcon: icon {{ "intro_country_dropdown", windowSubTextFg }};
stickersEmojiPickerCollapseIcon: icon {{ "intro_country_dropdown-flip_vertical", windowSubTextFg }};
stickersEmojiPickerExpandSize: 24px;
stickersEmojiPickerExpandBg: windowBgRipple;
stickersEmojiPickerBoxShadow: BoxShadow {
blurRadius: 20px;
offset: point(0px, 6px);
opacity: 0.22;
}
emojiStatusDefault: icon {{ "emoji/stickers_premium", emojiIconFg }}; emojiStatusDefault: icon {{ "emoji/stickers_premium", emojiIconFg }};
filtersRemove: IconButton(stickersRemove) { filtersRemove: IconButton(stickersRemove) {
@@ -609,7 +661,7 @@ sendBoxAlbumSmallGroupSize: size(30px, 25px);
sendBoxAlbumSmallGroupCircleSize: 27px; sendBoxAlbumSmallGroupCircleSize: 27px;
sendBoxFileGroupSkipTop: 2px; sendBoxFileGroupSkipTop: 2px;
sendBoxFileGroupSkipRight: 5px; sendBoxFileGroupSkipRight: 1px;
sendBoxFileGroupEditInternalSkip: -1px; sendBoxFileGroupEditInternalSkip: -1px;
sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) { sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
@@ -622,7 +674,7 @@ sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "send_media/send_media_cross", me
sendBoxAlbumButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg }}; sendBoxAlbumButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg }};
sendBoxAlbumGroupButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg, point(4px, 1px) }}; sendBoxAlbumGroupButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg, point(4px, 1px) }};
sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_cross", roundedFg, point(-2px, 1px) }}; sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_cross", roundedFg }};
defaultComposeIcons: ComposeIcons { defaultComposeIcons: ComposeIcons {
settings: icon {{ "emoji/emoji_settings", emojiIconFg }}; settings: icon {{ "emoji/emoji_settings", emojiIconFg }};
@@ -1033,6 +1085,19 @@ historyPinnedShowAll: IconButton(historyReplyCancel) {
icon: icon {{ "pinned_show_all", historyReplyCancelFg }}; icon: icon {{ "pinned_show_all", historyReplyCancelFg }};
iconOver: icon {{ "pinned_show_all", historyReplyCancelFgOver }}; iconOver: icon {{ "pinned_show_all", historyReplyCancelFgOver }};
} }
sendFilesReplyIconPosition: point(11px, 7px);
sendFilesReplyCancelSize: 24px;
sendFilesReplyCancel: IconButton(editMediaButton) {
width: sendFilesReplyCancelSize;
height: sendFilesReplyCancelSize;
icon: icon {{ "send_media/send_media_cross", historyReplyCancelFg }};
iconOver: icon {{ "send_media/send_media_cross", historyReplyCancelFgOver }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgRipple;
}
rippleAreaSize: sendFilesReplyCancelSize;
}
historyPinnedBotButton: RoundButton(defaultActiveButton) { historyPinnedBotButton: RoundButton(defaultActiveButton) {
width: -34px; width: -34px;
height: 30px; height: 30px;
@@ -670,12 +670,13 @@ void EmojiListWidget::applyNextSearchQuery() {
} }
const auto guard = gsl::finally([&] { finish(); }); const auto guard = gsl::finally([&] { finish(); });
auto plain = collectPlainSearchResults(); auto plain = collectPlainSearchResults();
if (_searchEmoji == _searchEmojiPrevious) {
return;
}
_searchEmoticon = QString(); _searchEmoticon = QString();
for (const auto emoji : plain) { {
_searchEmoticon += emoji->text(); auto exactSet = base::flat_set<EmojiPtr>();
const auto exact = SearchEmoji(_searchQuery, exactSet, true);
for (const auto emoji : exact) {
_searchEmoticon += emoji->text();
}
} }
_searchResults.clear(); _searchResults.clear();
_searchCustomIds.clear(); _searchCustomIds.clear();
@@ -690,9 +691,6 @@ void EmojiListWidget::applyNextSearchQuery() {
if (_mode != Mode::Full || session().premium()) { if (_mode != Mode::Full || session().premium()) {
appendPremiumSearchResults(); appendPremiumSearchResults();
} }
if (_mode == Mode::Full) {
appendLocalPackSearchResults();
}
_searchQueryText = ranges::accumulate( _searchQueryText = ranges::accumulate(
_searchQuery, _searchQuery,
@@ -822,62 +820,6 @@ void EmojiListWidget::appendPremiumSearchResults() {
} }
} }
void EmojiListWidget::appendLocalPackSearchResults() {
const auto text = _searchQueryText.toLower();
if (text.isEmpty()) {
return;
}
const auto test = session().isTestMode();
const auto &sets = session().data().stickers().sets();
const auto processSet = [&](uint64 setId) {
const auto it = sets.find(setId);
if (it == sets.end()) {
return;
}
const auto set = it->second.get();
if (!(set->flags & Data::StickersSetFlag::Emoji)) {
return;
}
const auto title = set->title.toLower();
if (!title.startsWith(text)
&& !title.contains(u' ' + text)) {
return;
}
const auto &list = set->stickers.empty()
? set->covers
: set->stickers;
for (const auto document : list) {
if (_searchResults.size() >= kCustomSearchLimit) {
return;
}
const auto sticker = document->sticker();
if (!sticker) {
continue;
}
const auto id = document->id;
if (!_searchCustomIds.emplace(id).second) {
continue;
}
const auto statusId = EmojiStatusId{ id };
_searchResults.push_back({
.custom = resolveCustomEmoji(
statusId,
document,
SearchEmojiSectionSetId()),
.id = { RecentEmojiDocument{ .id = id, .test = test } },
});
}
};
for (const auto setId
: session().data().stickers().emojiSetsOrder()) {
processSet(setId);
}
for (const auto setId
: session().data().stickers().featuredEmojiSetsOrder()) {
processSet(setId);
}
}
void EmojiListWidget::toggleSearchLoading(bool loading) { void EmojiListWidget::toggleSearchLoading(bool loading) {
if (_search) { if (_search) {
_search->setLoading(loading); _search->setLoading(loading);
@@ -1131,9 +1073,6 @@ void EmojiListWidget::showSearchResults() {
appendPremiumSearchResults(); appendPremiumSearchResults();
} }
fillCloudSearchResults(); fillCloudSearchResults();
if (_mode == Mode::Full) {
appendLocalPackSearchResults();
}
fillCloudSearchSets(); fillCloudSearchSets();
resizeToWidth(width()); resizeToWidth(width());
@@ -299,7 +299,6 @@ private:
void setupSearch(); void setupSearch();
[[nodiscard]] std::vector<EmojiPtr> collectPlainSearchResults(); [[nodiscard]] std::vector<EmojiPtr> collectPlainSearchResults();
void appendPremiumSearchResults(); void appendPremiumSearchResults();
void appendLocalPackSearchResults();
void sendSearchRequest(); void sendSearchRequest();
void sendSearchSetsRequest(const QString &query); void sendSearchSetsRequest(const QString &query);
void requestSearchCloud( void requestSearchCloud(
@@ -0,0 +1,691 @@
/*
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 "chat_helpers/emoji_picker_overlay.h"
#include "ui/abstract_button.h"
#include "ui/emoji_config.h"
#include "ui/painter.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "styles/style_chat_helpers.h"
#include <QtCore/QEvent>
#include <QtGui/QMouseEvent>
#include <QtGui/QPainter>
namespace ChatHelpers {
namespace {
[[nodiscard]] std::vector<EmojiPtr> BuildAllEmojis() {
using Section = Ui::Emoji::Section;
auto result = std::vector<EmojiPtr>();
for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) {
const auto section = Ui::Emoji::GetSection(Section(i));
result.reserve(result.size() + section.size());
for (const auto emoji : section) {
result.push_back(emoji);
}
}
return result;
}
[[nodiscard]] std::vector<EmojiPtr> DefaultRecentVector() {
const auto src = Ui::Emoji::GetDefaultRecent();
return std::vector<EmojiPtr>(src.begin(), src.end());
}
} // namespace
class EmojiPickerOverlay::Strip final : public Ui::RpWidget {
public:
Strip(QWidget *parent, not_null<EmojiPickerOverlay*> owner);
void refresh();
protected:
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private:
struct Cell {
EmojiPtr emoji = nullptr;
QRect rect;
};
[[nodiscard]] int cellAtPoint(QPoint p) const;
void updateHover(int index);
const not_null<EmojiPickerOverlay*> _owner;
std::vector<Cell> _cells;
int _hover = -1;
int _pressed = -1;
};
class EmojiPickerOverlay::Grid final : public Ui::RpWidget {
public:
Grid(QWidget *parent, not_null<EmojiPickerOverlay*> owner);
void setEmojis(std::vector<EmojiPtr> emojis);
int resizeGetHeight(int newWidth) override;
void refresh();
protected:
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private:
struct Cell {
EmojiPtr emoji = nullptr;
QRect rect;
};
[[nodiscard]] int cellAtPoint(QPoint p) const;
void relayoutCells();
void updateHover(int index);
const not_null<EmojiPickerOverlay*> _owner;
std::vector<EmojiPtr> _emojis;
std::vector<Cell> _cells;
int _columns = 0;
int _hover = -1;
int _pressed = -1;
};
namespace {
void DrawEmojiCell(
QPainter &p,
const QRect &cell,
EmojiPtr emoji,
bool selected,
bool hovered) {
if (selected) {
p.setPen(Qt::NoPen);
p.setBrush(st::stickersEmojiPickerSelectedBg);
p.drawEllipse(cell);
} else if (hovered) {
p.setPen(Qt::NoPen);
p.setBrush(anim::with_alpha(st::windowSubTextFg->c, 0.12));
p.drawEllipse(cell);
}
if (!emoji) {
return;
}
const auto esize = Ui::Emoji::GetSizeLarge();
const auto dpr = style::DevicePixelRatio();
const auto pixelSize = esize / dpr;
const auto drawSize = std::min(
pixelSize,
st::stickersEmojiPickerItemSize - 4);
const auto x = cell.x() + (cell.width() - drawSize) / 2;
const auto y = cell.y() + (cell.height() - drawSize) / 2;
if (drawSize == pixelSize) {
Ui::Emoji::Draw(p, emoji, esize, x, y);
} else {
const auto target = QRect(x, y, drawSize, drawSize);
auto buffer = QImage(
QSize(pixelSize, pixelSize) * dpr,
QImage::Format_ARGB32_Premultiplied);
buffer.fill(Qt::transparent);
buffer.setDevicePixelRatio(dpr);
{
auto q = QPainter(&buffer);
Ui::Emoji::Draw(q, emoji, esize, 0, 0);
}
auto hq = PainterHighQualityEnabler(p);
p.drawImage(target, buffer);
}
}
} // namespace
EmojiPickerOverlay::Strip::Strip(
QWidget *parent,
not_null<EmojiPickerOverlay*> owner)
: RpWidget(parent)
, _owner(owner) {
setMouseTracking(true);
}
void EmojiPickerOverlay::Strip::refresh() {
const auto &sel = _owner->_selectedList;
const auto &recent = _owner->_recent;
const auto item = st::stickersEmojiPickerItemSize;
const auto skip = st::stickersEmojiPickerItemSkip;
const auto w = width();
if (w <= 0 || item <= 0) {
_cells.clear();
update();
return;
}
const auto capacity = std::max(1, (w + skip) / (item + skip));
auto order = std::vector<EmojiPtr>();
order.reserve(sel.size() + recent.size());
for (const auto emoji : sel) {
order.push_back(emoji);
}
for (const auto emoji : recent) {
const auto already = std::find(sel.begin(), sel.end(), emoji)
!= sel.end();
if (!already) {
order.push_back(emoji);
}
}
if (int(order.size()) > capacity) {
order.resize(capacity);
}
_cells.clear();
_cells.reserve(order.size());
auto x = 0;
const auto y = (height() - item) / 2;
for (const auto emoji : order) {
_cells.push_back({ emoji, QRect(x, y, item, item) });
x += item + skip;
}
_hover = -1;
_pressed = -1;
update();
}
int EmojiPickerOverlay::Strip::cellAtPoint(QPoint p) const {
for (auto i = 0; i != int(_cells.size()); ++i) {
if (_cells[i].rect.contains(p)) {
return i;
}
}
return -1;
}
void EmojiPickerOverlay::Strip::updateHover(int index) {
if (_hover == index) {
return;
}
_hover = index;
update();
}
void EmojiPickerOverlay::Strip::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
for (auto i = 0; i != int(_cells.size()); ++i) {
const auto &cell = _cells[i];
const auto selected = _owner->_selectedList.end()
!= std::find(
_owner->_selectedList.begin(),
_owner->_selectedList.end(),
cell.emoji);
DrawEmojiCell(p, cell.rect, cell.emoji, selected, i == _hover);
}
}
void EmojiPickerOverlay::Strip::mouseMoveEvent(QMouseEvent *e) {
updateHover(cellAtPoint(e->pos()));
}
void EmojiPickerOverlay::Strip::mousePressEvent(QMouseEvent *e) {
_pressed = cellAtPoint(e->pos());
}
void EmojiPickerOverlay::Strip::mouseReleaseEvent(QMouseEvent *e) {
const auto released = cellAtPoint(e->pos());
const auto index = _pressed;
_pressed = -1;
if (released == index && index >= 0 && index < int(_cells.size())) {
_owner->toggleEmoji(_cells[index].emoji, false);
}
}
void EmojiPickerOverlay::Strip::leaveEventHook(QEvent *e) {
updateHover(-1);
}
EmojiPickerOverlay::Grid::Grid(
QWidget *parent,
not_null<EmojiPickerOverlay*> owner)
: RpWidget(parent)
, _owner(owner) {
setMouseTracking(true);
}
void EmojiPickerOverlay::Grid::setEmojis(std::vector<EmojiPtr> emojis) {
_emojis = std::move(emojis);
relayoutCells();
}
int EmojiPickerOverlay::Grid::resizeGetHeight(int newWidth) {
resize(newWidth, 0);
relayoutCells();
return height();
}
void EmojiPickerOverlay::Grid::refresh() {
update();
}
void EmojiPickerOverlay::Grid::relayoutCells() {
const auto item = st::stickersEmojiPickerItemSize;
const auto skip = st::stickersEmojiPickerItemSkip;
const auto w = width();
_columns = std::max(1, (w + skip) / (item + skip));
_cells.clear();
_cells.reserve(_emojis.size());
auto col = 0;
auto row = 0;
for (const auto emoji : _emojis) {
const auto x = col * (item + skip);
const auto y = row * (item + skip);
_cells.push_back({ emoji, QRect(x, y, item, item) });
if (++col >= _columns) {
col = 0;
++row;
}
}
const auto fullRows = row + (col > 0 ? 1 : 0);
const auto h = fullRows > 0
? (fullRows * item + (fullRows - 1) * skip)
: 0;
resize(w, h);
_hover = -1;
_pressed = -1;
update();
}
int EmojiPickerOverlay::Grid::cellAtPoint(QPoint p) const {
for (auto i = 0; i != int(_cells.size()); ++i) {
if (_cells[i].rect.contains(p)) {
return i;
}
}
return -1;
}
void EmojiPickerOverlay::Grid::updateHover(int index) {
if (_hover == index) {
return;
}
_hover = index;
update();
}
void EmojiPickerOverlay::Grid::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto clip = e->rect();
for (auto i = 0; i != int(_cells.size()); ++i) {
const auto &cell = _cells[i];
if (!cell.rect.intersects(clip)) {
continue;
}
const auto selected = _owner->_selectedList.end()
!= std::find(
_owner->_selectedList.begin(),
_owner->_selectedList.end(),
cell.emoji);
DrawEmojiCell(p, cell.rect, cell.emoji, selected, i == _hover);
}
}
void EmojiPickerOverlay::Grid::mouseMoveEvent(QMouseEvent *e) {
updateHover(cellAtPoint(e->pos()));
}
void EmojiPickerOverlay::Grid::mousePressEvent(QMouseEvent *e) {
_pressed = cellAtPoint(e->pos());
}
void EmojiPickerOverlay::Grid::mouseReleaseEvent(QMouseEvent *e) {
const auto released = cellAtPoint(e->pos());
const auto index = _pressed;
_pressed = -1;
if (released == index && index >= 0 && index < int(_cells.size())) {
_owner->toggleEmoji(_cells[index].emoji, true);
}
}
void EmojiPickerOverlay::Grid::leaveEventHook(QEvent *e) {
updateHover(-1);
}
EmojiPickerOverlay::Metrics EmojiPickerOverlay::EstimateMetrics(
const QString &aboutText) {
const auto tailHeight = st::stickersEmojiPickerStripBubble.height();
const auto shadowExtent = Ui::BoxShadow::ExtendFor(
st::stickersEmojiPickerBoxShadow);
const auto &pad = st::stickersEmojiPickerPadding;
auto about = Ui::FlatLabel(
nullptr,
aboutText,
st::stickersEmojiPickerAbout);
const auto collapsedHeight = pad.top()
+ about.height()
+ st::stickersEmojiPickerStripHeight
+ pad.bottom();
const auto expandedHeight = collapsedHeight
+ st::stickersEmojiPickerExpandedHeight;
const auto shadowAndTail = shadowExtent.top()
+ shadowExtent.bottom()
+ tailHeight;
return {
.shadowExtent = shadowExtent,
.tailHeight = tailHeight,
.collapsedHeight = collapsedHeight,
.expandedHeight = expandedHeight,
.totalCollapsedHeight = collapsedHeight + shadowAndTail,
.totalExpandedHeight = expandedHeight + shadowAndTail,
};
}
EmojiPickerOverlay::EmojiPickerOverlay(
QWidget *parent,
EmojiPickerOverlayDescriptor descriptor)
: RpWidget(parent)
, _aboutText(std::move(descriptor.aboutText))
, _recent(descriptor.recent.empty()
? DefaultRecentVector()
: std::move(descriptor.recent))
, _maxSelected(descriptor.maxSelected)
, _allowExpand(descriptor.allowExpand)
, _selectedList(std::move(descriptor.initialSelected))
, _shadow(st::stickersEmojiPickerBoxShadow) {
_allForGrid = BuildAllEmojis();
_about = std::make_unique<Ui::FlatLabel>(
this,
_aboutText,
st::stickersEmojiPickerAbout);
_strip = Ui::CreateChild<Strip>(this, this);
if (_allowExpand) {
_expandButton = Ui::CreateChild<Ui::AbstractButton>(this);
_expandButton->resize(
st::stickersEmojiPickerExpandSize,
st::stickersEmojiPickerExpandSize);
_expandButton->setClickedCallback([=] {
setExpanded(!_expanded.current());
});
_expandButton->paintRequest(
) | rpl::on_next([=](const QRect &clip) {
auto p = QPainter(_expandButton);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::stickersEmojiPickerExpandBg);
p.drawEllipse(_expandButton->rect());
const auto &icon = _expanded.current()
? st::stickersEmojiPickerCollapseIcon
: st::stickersEmojiPickerExpandIcon;
const auto x = (_expandButton->width() - icon.width()) / 2;
const auto y = (_expandButton->height() - icon.height()) / 2;
icon.paint(p, x, y, _expandButton->width());
}, _expandButton->lifetime());
_scroll = std::make_unique<Ui::ScrollArea>(
this,
st::stickersEmojiPickerScroll);
_scroll->setFrameStyle(QFrame::NoFrame);
_scroll->hide();
const auto gridPtr = _scroll->setOwnedWidget(
object_ptr<Grid>(_scroll.get(), this));
_grid = gridPtr.data();
_grid->setEmojis(_allForGrid);
}
_selectedVar = _selectedList;
resize(width(), totalExpandedHeight());
}
QMargins EmojiPickerOverlay::shadowExtent() const {
return _shadow.extend();
}
int EmojiPickerOverlay::totalCollapsedHeight() const {
const auto ext = _shadow.extend();
return collapsedHeight() + ext.top() + ext.bottom() + tailHeight();
}
int EmojiPickerOverlay::totalExpandedHeight() const {
const auto ext = _shadow.extend();
return expandedHeight() + ext.top() + ext.bottom() + tailHeight();
}
QRect EmojiPickerOverlay::bubbleRect() const {
const auto ext = _shadow.extend();
return QRect(
ext.left(),
ext.top(),
width() - ext.left() - ext.right(),
height() - ext.top() - ext.bottom() - tailHeight());
}
QRect EmojiPickerOverlay::bubbleShownRect() const {
const auto r = bubbleRect();
return QRect(r.x(), r.y(), r.width(), currentShownHeight());
}
EmojiPickerOverlay::~EmojiPickerOverlay() = default;
const std::vector<EmojiPtr> &EmojiPickerOverlay::selected() const {
return _selectedList;
}
rpl::producer<std::vector<EmojiPtr>>
EmojiPickerOverlay::selectedValue() const {
return _selectedVar.value();
}
void EmojiPickerOverlay::setExpanded(bool expanded) {
if (!_allowExpand || _expanded.current() == expanded) {
return;
}
startExpandAnimation(expanded);
_expanded = expanded;
if (_expandButton) {
_expandButton->update();
}
}
void EmojiPickerOverlay::startExpandAnimation(bool expanded) {
const auto from = _expandAnim.value(expanded ? 0. : 1.);
const auto to = expanded ? 1. : 0.;
_expandAnim.start(
[=] { applyExpandProgress(); },
from,
to,
st::slideWrapDuration,
anim::easeOutCirc);
applyExpandProgress();
}
float64 EmojiPickerOverlay::currentExpandValue() const {
return _expandAnim.value(_expanded.current() ? 1. : 0.);
}
int EmojiPickerOverlay::currentShownHeight() const {
const auto progress = currentExpandValue();
return anim::interpolate(
collapsedHeight(),
expandedHeight(),
progress);
}
void EmojiPickerOverlay::applyExpandProgress() {
if (_scroll) {
const auto progress = currentExpandValue();
_scroll->setVisible(progress > 0.);
}
relayout();
update();
}
bool EmojiPickerOverlay::expanded() const {
return _expanded.current();
}
rpl::producer<bool> EmojiPickerOverlay::expandedValue() const {
return _expanded.value();
}
int EmojiPickerOverlay::collapsedHeight() const {
const auto &pad = st::stickersEmojiPickerPadding;
const auto aboutH = _about ? _about->height() : 0;
return pad.top()
+ aboutH
+ st::stickersEmojiPickerStripHeight
+ pad.bottom();
}
int EmojiPickerOverlay::expandedHeight() const {
return collapsedHeight() + st::stickersEmojiPickerExpandedHeight;
}
void EmojiPickerOverlay::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
auto hq = PainterHighQualityEnabler(p);
const auto progress = currentExpandValue();
const auto shown = bubbleShownRect();
const auto radius = st::stickersEmojiPickerExpandedRadius;
_shadow.paint(p, shown, radius);
p.setPen(Qt::NoPen);
p.setBrush(st::stickersEmojiPickerBg);
p.drawRoundedRect(shown, radius, radius);
if (progress < 1.) {
paintTailBubble(p, shown, 1. - progress);
}
}
void EmojiPickerOverlay::paintTailBubble(
QPainter &p,
const QRect &bubble,
float64 opacity) {
const auto &icon = st::stickersEmojiPickerStripBubble;
const auto offsetRight = st::stickersEmojiPickerStripBubbleRight;
const auto x = bubble.right() + 1 - offsetRight - icon.width();
const auto y = bubble.bottom() + 1;
if (opacity >= 1.) {
icon.paint(p, x, y, width());
} else {
p.save();
p.setOpacity(opacity);
icon.paint(p, x, y, width());
p.restore();
}
}
void EmojiPickerOverlay::resizeEvent(QResizeEvent *e) {
relayout();
}
void EmojiPickerOverlay::mousePressEvent(QMouseEvent *e) {
if (!bubbleShownRect().contains(e->pos())) {
e->ignore();
}
}
int EmojiPickerOverlay::tailHeight() const {
return st::stickersEmojiPickerStripBubble.height();
}
void EmojiPickerOverlay::relayout() {
const auto &pad = st::stickersEmojiPickerPadding;
const auto bubble = bubbleRect();
const auto bubbleShown = currentShownHeight();
if (_about) {
_about->resizeToWidth(bubble.width() - pad.left() - pad.right());
_about->moveToLeft(bubble.left() + pad.left(), bubble.top() + pad.top());
}
const auto aboutBottom = _about
? (_about->y() + _about->height())
: (bubble.top() + pad.top());
const auto stripTop = aboutBottom;
const auto stripH = st::stickersEmojiPickerStripHeight;
const auto expandSize = _expandButton
? _expandButton->width()
: 0;
const auto expandGap = _expandButton
? st::stickersEmojiPickerItemSkip
: 0;
const auto stripW = bubble.width()
- pad.left()
- pad.right()
- expandSize
- expandGap;
_strip->setGeometry(bubble.left() + pad.left(), stripTop, stripW, stripH);
_strip->refresh();
if (_expandButton) {
const auto bx = bubble.right() + 1 - pad.right() - expandSize;
const auto by = stripTop + (stripH - expandSize) / 2;
_expandButton->moveToLeft(bx, by);
}
if (_scroll) {
const auto scrollTop = stripTop + stripH;
const auto bubbleBottom = bubble.top() + bubbleShown;
const auto scrollH = std::max(
0,
bubbleBottom - scrollTop - pad.bottom());
const auto scrollContentWidth = bubble.width()
- pad.left()
- pad.right();
const auto scrollAreaWidth = scrollContentWidth
+ pad.right();
_scroll->setGeometry(
bubble.left() + pad.left(),
scrollTop,
scrollAreaWidth,
scrollH);
if (_grid) {
_grid->resizeGetHeight(scrollContentWidth);
}
}
}
void EmojiPickerOverlay::toggleEmoji(EmojiPtr emoji, bool fromGrid) {
if (!emoji) {
return;
}
const auto it = std::find(
_selectedList.begin(),
_selectedList.end(),
emoji);
if (it != _selectedList.end()) {
_selectedList.erase(it);
} else {
if (_maxSelected > 0 && int(_selectedList.size()) >= _maxSelected) {
return;
}
_selectedList.push_back(emoji);
}
notifySelectionChanged();
if (fromGrid) {
setExpanded(false);
}
}
void EmojiPickerOverlay::notifySelectionChanged() {
_selectedVar = _selectedList;
if (_strip) {
_strip->refresh();
}
if (_grid) {
_grid->refresh();
}
}
} // namespace ChatHelpers
@@ -0,0 +1,103 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "ui/emoji_config.h"
#include "ui/widgets/shadow.h"
namespace Ui {
class AbstractButton;
class FlatLabel;
class ScrollArea;
} // namespace Ui
namespace ChatHelpers {
struct EmojiPickerOverlayDescriptor {
QString aboutText;
std::vector<EmojiPtr> recent;
int maxSelected = 0;
bool allowExpand = true;
std::vector<EmojiPtr> initialSelected;
};
class EmojiPickerOverlay final : public Ui::RpWidget {
public:
EmojiPickerOverlay(
QWidget *parent,
EmojiPickerOverlayDescriptor descriptor);
~EmojiPickerOverlay();
struct Metrics {
QMargins shadowExtent;
int tailHeight = 0;
int collapsedHeight = 0;
int expandedHeight = 0;
int totalCollapsedHeight = 0;
int totalExpandedHeight = 0;
};
[[nodiscard]] static Metrics EstimateMetrics(const QString &aboutText);
[[nodiscard]] const std::vector<EmojiPtr> &selected() const;
[[nodiscard]] rpl::producer<std::vector<EmojiPtr>> selectedValue() const;
void setExpanded(bool expanded);
[[nodiscard]] bool expanded() const;
[[nodiscard]] rpl::producer<bool> expandedValue() const;
[[nodiscard]] int collapsedHeight() const;
[[nodiscard]] int expandedHeight() const;
[[nodiscard]] QMargins shadowExtent() const;
[[nodiscard]] int totalCollapsedHeight() const;
[[nodiscard]] int totalExpandedHeight() const;
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
private:
class Strip;
class Grid;
void relayout();
void toggleEmoji(EmojiPtr emoji, bool fromGrid);
void notifySelectionChanged();
void startExpandAnimation(bool expanded);
void applyExpandProgress();
void paintTailBubble(QPainter &p, const QRect &bubble, float64 opacity);
[[nodiscard]] float64 currentExpandValue() const;
[[nodiscard]] int currentShownHeight() const;
[[nodiscard]] int tailHeight() const;
[[nodiscard]] QRect bubbleRect() const;
[[nodiscard]] QRect bubbleShownRect() const;
const QString _aboutText;
const std::vector<EmojiPtr> _recent;
const int _maxSelected;
const bool _allowExpand;
std::vector<EmojiPtr> _allForGrid;
std::vector<EmojiPtr> _selectedList;
rpl::variable<std::vector<EmojiPtr>> _selectedVar;
rpl::variable<bool> _expanded = false;
std::unique_ptr<Ui::FlatLabel> _about;
Strip *_strip = nullptr;
Ui::AbstractButton *_expandButton = nullptr;
std::unique_ptr<Ui::ScrollArea> _scroll;
Grid *_grid = nullptr;
Ui::Animations::Simple _expandAnim;
Ui::BoxShadow _shadow;
};
} // namespace ChatHelpers
@@ -146,7 +146,8 @@ rpl::producer<std::vector<GifSection>> GifSectionsValue(
[[nodiscard]] std::vector<EmojiPtr> SearchEmoji( [[nodiscard]] std::vector<EmojiPtr> SearchEmoji(
const std::vector<QString> &query, const std::vector<QString> &query,
base::flat_set<EmojiPtr> &outResultSet) { base::flat_set<EmojiPtr> &outResultSet,
bool exact) {
auto result = std::vector<EmojiPtr>(); auto result = std::vector<EmojiPtr>();
const auto pushPlain = [&](EmojiPtr emoji) { const auto pushPlain = [&](EmojiPtr emoji) {
if (result.size() < kEmojiSearchLimit if (result.size() < kEmojiSearchLimit
@@ -170,7 +171,7 @@ rpl::producer<std::vector<GifSection>> GifSectionsValue(
refreshed = true; refreshed = true;
keywords.refresh(); keywords.refresh();
} }
const auto list = keywords.queryMine(entry); const auto list = keywords.queryMine(entry, exact);
for (const auto &entry : list) { for (const auto &entry : list) {
pushPlain(entry.emoji); pushPlain(entry.emoji);
if (result.size() >= kEmojiSearchLimit) { if (result.size() >= kEmojiSearchLimit) {
@@ -70,7 +70,8 @@ struct GifSection {
[[nodiscard]] std::vector<EmojiPtr> SearchEmoji( [[nodiscard]] std::vector<EmojiPtr> SearchEmoji(
const std::vector<QString> &query, const std::vector<QString> &query,
base::flat_set<EmojiPtr> &outResultSet); base::flat_set<EmojiPtr> &outResultSet,
bool exact = false);
struct StickerIcon { struct StickerIcon {
explicit StickerIcon(uint64 setId); explicit StickerIcon(uint64 setId);
@@ -215,6 +215,7 @@ StickersListWidget::StickersListWidget(
, _section(Section::Stickers) , _section(Section::Stickers)
, _isMasks(_mode == Mode::Masks) , _isMasks(_mode == Mode::Masks)
, _isEffects(_mode == Mode::MessageEffects) , _isEffects(_mode == Mode::MessageEffects)
, _excludeSetId(descriptor.excludeSetId)
, _updateItemsTimer([=] { updateItems(); }) , _updateItemsTimer([=] { updateItems(); })
, _updateSetsTimer([=] { updateSets(); }) , _updateSetsTimer([=] { updateSets(); })
, _trendingAddBgOver( , _trendingAddBgOver(
@@ -2580,6 +2581,9 @@ bool StickersListWidget::appendSet(
uint64 setId, uint64 setId,
bool externalLayout, bool externalLayout,
AppendSkip skip) { AppendSkip skip) {
if (_excludeSetId && setId == _excludeSetId) {
return false;
}
const auto &sets = session().data().stickers().sets(); const auto &sets = session().data().stickers().sets();
auto it = sets.find(setId); auto it = sets.find(setId);
if (it == sets.cend() if (it == sets.cend()
@@ -84,6 +84,7 @@ struct StickersListDescriptor {
std::vector<StickerCustomRecentDescriptor> customRecentList; std::vector<StickerCustomRecentDescriptor> customRecentList;
const style::EmojiPan *st = nullptr; const style::EmojiPan *st = nullptr;
ComposeFeatures features; ComposeFeatures features;
uint64 excludeSetId = 0;
}; };
class StickersListWidget final : public TabbedSelector::Inner { class StickersListWidget final : public TabbedSelector::Inner {
@@ -419,6 +420,7 @@ private:
Section _section = Section::Stickers; Section _section = Section::Stickers;
const bool _isMasks; const bool _isMasks;
const bool _isEffects; const bool _isEffects;
const uint64 _excludeSetId = 0;
base::Timer _updateItemsTimer; base::Timer _updateItemsTimer;
base::Timer _updateSetsTimer; base::Timer _updateSetsTimer;
@@ -192,6 +192,10 @@ void TabbedPanel::setDesiredHeightValues(
updateContentHeight(); updateContentHeight();
} }
void TabbedPanel::setShowAnimationOrigin(Ui::PanelAnimation::Origin origin) {
_showAnimationOrigin = origin;
}
void TabbedPanel::setDropDown(bool dropDown) { void TabbedPanel::setDropDown(bool dropDown) {
selector()->setDropDown(dropDown); selector()->setDropDown(dropDown);
_dropDown = dropDown; _dropDown = dropDown;
@@ -380,11 +384,12 @@ void TabbedPanel::startShowAnimation() {
if (!_a_show.animating()) { if (!_a_show.animating()) {
auto image = grabForAnimation(); auto image = grabForAnimation();
const auto origin = _showAnimationOrigin.value_or(_dropDown
? Ui::PanelAnimation::Origin::TopRight
: Ui::PanelAnimation::Origin::BottomRight);
_showAnimation = std::make_unique<Ui::PanelAnimation>( _showAnimation = std::make_unique<Ui::PanelAnimation>(
_selector->st().showAnimation, _selector->st().showAnimation,
(_dropDown origin);
? Ui::PanelAnimation::Origin::TopRight
: Ui::PanelAnimation::Origin::BottomRight));
auto inner = rect().marginsRemoved(st::emojiPanMargins); auto inner = rect().marginsRemoved(st::emojiPanMargins);
_showAnimation->setFinalImage( _showAnimation->setFinalImage(
std::move(image), std::move(image),
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "base/timer.h" #include "base/timer.h"
@@ -17,9 +18,6 @@ namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
namespace Ui {
class PanelAnimation;
} // namespace Ui
namespace ChatHelpers { namespace ChatHelpers {
@@ -57,6 +55,7 @@ public:
int minHeight, int minHeight,
int maxHeight); int maxHeight);
void setDropDown(bool dropDown); void setDropDown(bool dropDown);
void setShowAnimationOrigin(Ui::PanelAnimation::Origin origin);
void hideFast(); void hideFast();
bool hiding() const { bool hiding() const {
@@ -122,6 +121,7 @@ private:
bool _shouldFinishHide = false; bool _shouldFinishHide = false;
bool _dropDown = false; bool _dropDown = false;
std::optional<Ui::PanelAnimation::Origin> _showAnimationOrigin;
bool _hiding = false; bool _hiding = false;
bool _hideAfterSlide = false; bool _hideAfterSlide = false;
@@ -381,6 +381,7 @@ TabbedSelector::TabbedSelector(
, _show(std::move(descriptor.show)) , _show(std::move(descriptor.show))
, _level(descriptor.level) , _level(descriptor.level)
, _customTextColor(std::move(descriptor.customTextColor)) , _customTextColor(std::move(descriptor.customTextColor))
, _excludeStickerSetId(descriptor.excludeStickerSetId)
, _mode(descriptor.mode) , _mode(descriptor.mode)
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg)) , _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
, _categoriesRounding( , _categoriesRounding(
@@ -644,6 +645,7 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.paused = paused, .paused = paused,
.st = &_st, .st = &_st,
.features = _features, .features = _features,
.excludeSetId = _excludeStickerSetId,
}); });
} }
case SelectorTab::Gifs: { case SelectorTab::Gifs: {
@@ -100,6 +100,7 @@ struct TabbedSelectorDescriptor {
TabbedSelectorMode mode = TabbedSelectorMode::Full; TabbedSelectorMode mode = TabbedSelectorMode::Full;
Fn<QColor()> customTextColor; Fn<QColor()> customTextColor;
ComposeFeatures features; ComposeFeatures features;
uint64 excludeStickerSetId = 0;
}; };
enum class TabbedSearchType { enum class TabbedSearchType {
@@ -295,6 +296,7 @@ private:
const std::shared_ptr<Show> _show; const std::shared_ptr<Show> _show;
const PauseReason _level = {}; const PauseReason _level = {};
const Fn<QColor()> _customTextColor; const Fn<QColor()> _customTextColor;
const uint64 _excludeStickerSetId = 0;
Ui::Controls::SwipeBackResult _swipeBackData; Ui::Controls::SwipeBackResult _swipeBackData;
+15
Ver Arquivo
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/sandbox.h" #include "core/sandbox.h"
#include "core/local_url_handlers.h" #include "core/local_url_handlers.h"
#include "core/launcher.h" #include "core/launcher.h"
#include "core/proxy_rotation_manager.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "chat_helpers/emoji_keywords.h" #include "chat_helpers/emoji_keywords.h"
#include "chat_helpers/stickers_emoji_image_loader.h" #include "chat_helpers/stickers_emoji_image_loader.h"
@@ -146,6 +147,7 @@ struct Application::Private {
base::Timer quitTimer; base::Timer quitTimer;
UiIntegration uiIntegration; UiIntegration uiIntegration;
Settings settings; Settings settings;
std::unique_ptr<ProxyRotationManager> proxyRotation;
}; };
Application::Application() Application::Application()
@@ -173,6 +175,7 @@ Application::Application()
, _setupEmailLock(false) , _setupEmailLock(false)
, _autoLockTimer([=] { checkAutoLock(); }) { , _autoLockTimer([=] { checkAutoLock(); }) {
Ui::Integration::Set(&_private->uiIntegration); Ui::Integration::Set(&_private->uiIntegration);
_private->proxyRotation = std::make_unique<ProxyRotationManager>();
_platformIntegration->init(); _platformIntegration->init();
@@ -234,6 +237,7 @@ Application::~Application() {
// Domain::finish() and there is a violation on Ensures(started()). // Domain::finish() and there is a violation on Ensures(started()).
closeAdditionalWindows(); closeAdditionalWindows();
_private->proxyRotation = nullptr;
_domain->finish(); _domain->finish();
Local::finish(); Local::finish();
@@ -835,6 +839,17 @@ void Application::setCurrentProxy(
refreshGlobalProxy(); refreshGlobalProxy();
_proxyChanges.fire({ was, now }); _proxyChanges.fire({ was, now });
my.connectionTypeChangesNotify(); my.connectionTypeChangesNotify();
proxyRotationSettingsChanged();
}
void Application::proxyRotationSettingsChanged() {
_private->proxyRotation->settingsChanged();
}
void Application::checkProxyRotation(
not_null<Main::Account*> account,
int32 state) {
_private->proxyRotation->handleConnectionStateChanged(account, state);
} }
auto Application::proxyChanges() const -> rpl::producer<ProxyChange> { auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
+2
Ver Arquivo
@@ -219,6 +219,8 @@ public:
void setCurrentProxy( void setCurrentProxy(
const MTP::ProxyData &proxy, const MTP::ProxyData &proxy,
MTP::ProxyData::Settings settings); MTP::ProxyData::Settings settings);
void proxyRotationSettingsChanged();
void checkProxyRotation(not_null<Main::Account*> account, int32 state);
[[nodiscard]] rpl::producer<ProxyChange> proxyChanges() const; [[nodiscard]] rpl::producer<ProxyChange> proxyChanges() const;
void badMtprotoConfigurationError(); void badMtprotoConfigurationError();
+161 -3
Ver Arquivo
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "storage/serialize_common.h" #include "storage/serialize_common.h"
#include <algorithm>
namespace Core { namespace Core {
namespace { namespace {
@@ -88,6 +90,22 @@ namespace {
return result; return result;
} }
std::vector<int> NormalizeProxyRotationPreferredIndices(
std::vector<int> indices,
int listSize) {
auto filtered = std::vector<int>();
filtered.reserve(indices.size());
for (const auto index : indices) {
if (index < 0
|| index >= listSize
|| ranges::contains(filtered, index)) {
continue;
}
filtered.push_back(index);
}
return filtered;
}
} // namespace } // namespace
SettingsProxy::SettingsProxy() SettingsProxy::SettingsProxy()
@@ -108,7 +126,7 @@ QByteArray SettingsProxy::serialize() const {
0, 0,
ranges::plus(), ranges::plus(),
&Serialize::bytearraySize) &Serialize::bytearraySize)
+ 1 * sizeof(qint32); // _checkIpWarningShown + (4 + int(_proxyRotationPreferredIndices.size())) * sizeof(qint32);
auto stream = Serialize::ByteArrayWriter(size); auto stream = Serialize::ByteArrayWriter(size);
stream stream
<< qint32(_tryIPv6 ? 1 : 0) << qint32(_tryIPv6 ? 1 : 0)
@@ -119,7 +137,14 @@ QByteArray SettingsProxy::serialize() const {
for (const auto &i : serializedList) { for (const auto &i : serializedList) {
stream << i; stream << i;
} }
stream << qint32(_checkIpWarningShown ? 1 : 0); stream
<< qint32(_checkIpWarningShown ? 1 : 0)
<< qint32(_proxyRotationEnabled ? 1 : 0)
<< qint32(_proxyRotationTimeout)
<< qint32(_proxyRotationPreferredIndices.size());
for (const auto index : _proxyRotationPreferredIndices) {
stream << qint32(index);
}
return std::move(stream).result(); return std::move(stream).result();
} }
@@ -135,6 +160,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
auto settings = ProxySettingsToInt(_settings); auto settings = ProxySettingsToInt(_settings);
auto listCount = qint32(_list.size()); auto listCount = qint32(_list.size());
auto selectedProxy = QByteArray(); auto selectedProxy = QByteArray();
auto list = std::vector<MTP::ProxyData>();
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream stream
@@ -144,10 +170,14 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
>> selectedProxy >> selectedProxy
>> listCount; >> listCount;
if (stream.ok()) { if (stream.ok()) {
if (listCount < 0) {
return false;
}
list.reserve(listCount);
for (auto i = 0; i != listCount; ++i) { for (auto i = 0; i != listCount; ++i) {
QByteArray data; QByteArray data;
stream >> data; stream >> data;
_list.push_back(DeserializeProxyData(data)); list.push_back(DeserializeProxyData(data));
} }
} }
} }
@@ -156,6 +186,30 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> checkIpWarningShown; stream >> checkIpWarningShown;
} }
auto proxyRotationEnabled = qint32(_proxyRotationEnabled ? 1 : 0);
if (!stream.atEnd()) {
stream >> proxyRotationEnabled;
}
auto proxyRotationTimeout = qint32(_proxyRotationTimeout);
if (!stream.atEnd()) {
stream >> proxyRotationTimeout;
}
auto preferredCount = qint32(0);
auto preferredIndices = std::vector<int>();
if (!stream.atEnd()) {
stream >> preferredCount;
if (stream.ok()) {
if (preferredCount < 0) {
return false;
}
preferredIndices.reserve(preferredCount);
for (auto i = 0; i != preferredCount; ++i) {
auto index = qint32(0);
stream >> index;
preferredIndices.push_back(index);
}
}
}
if (!stream.ok()) { if (!stream.ok()) {
LOG(("App Error: " LOG(("App Error: "
@@ -166,8 +220,12 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
_tryIPv6 = (tryIPv6 == 1); _tryIPv6 = (tryIPv6 == 1);
_useProxyForCalls = (useProxyForCalls == 1); _useProxyForCalls = (useProxyForCalls == 1);
_checkIpWarningShown = (checkIpWarningShown == 1); _checkIpWarningShown = (checkIpWarningShown == 1);
_proxyRotationEnabled = (proxyRotationEnabled == 1);
setProxyRotationTimeout(proxyRotationTimeout);
_settings = IntToProxySettings(settings); _settings = IntToProxySettings(settings);
_selected = DeserializeProxyData(selectedProxy); _selected = DeserializeProxyData(selectedProxy);
_list = std::move(list);
setProxyRotationPreferredIndices(std::move(preferredIndices));
return true; return true;
} }
@@ -192,6 +250,32 @@ void SettingsProxy::setCheckIpWarningShown(bool value) {
_checkIpWarningShown = value; _checkIpWarningShown = value;
} }
const std::vector<int> &SettingsProxy::proxyRotationPreferredIndices() const {
return _proxyRotationPreferredIndices;
}
void SettingsProxy::setProxyRotationPreferredIndices(std::vector<int> value) {
_proxyRotationPreferredIndices = NormalizeProxyRotationPreferredIndices(
std::move(value),
int(_list.size()));
}
bool SettingsProxy::promoteProxyRotationPreferredIndex(int index) {
if (index < 0 || index >= int(_list.size())) {
return false;
}
auto &indices = _proxyRotationPreferredIndices;
const auto i = ranges::find(indices, index);
if (i == begin(indices)) {
return false;
} else if (i != end(indices)) {
std::rotate(begin(indices), i, std::next(i));
} else {
indices.insert(begin(indices), index);
}
return true;
}
bool SettingsProxy::tryIPv6() const { bool SettingsProxy::tryIPv6() const {
return _tryIPv6; return _tryIPv6;
} }
@@ -208,6 +292,24 @@ void SettingsProxy::setUseProxyForCalls(bool value) {
_useProxyForCalls = value; _useProxyForCalls = value;
} }
bool SettingsProxy::proxyRotationEnabled() const {
return _proxyRotationEnabled;
}
void SettingsProxy::setProxyRotationEnabled(bool value) {
_proxyRotationEnabled = value;
}
int SettingsProxy::proxyRotationTimeout() const {
return _proxyRotationTimeout;
}
void SettingsProxy::setProxyRotationTimeout(int value) {
_proxyRotationTimeout = (value > 0)
? value
: kDefaultProxyRotationTimeout;
}
MTP::ProxyData::Settings SettingsProxy::settings() const { MTP::ProxyData::Settings SettingsProxy::settings() const {
return _settings; return _settings;
} }
@@ -232,6 +334,62 @@ std::vector<MTP::ProxyData> &SettingsProxy::list() {
return _list; return _list;
} }
void SettingsProxy::setList(std::vector<MTP::ProxyData> value) {
_list = std::move(value);
_proxyRotationPreferredIndices.clear();
}
void SettingsProxy::addToList(MTP::ProxyData value) {
_list.push_back(std::move(value));
}
void SettingsProxy::insertToList(int index, MTP::ProxyData value) {
index = std::clamp(index, 0, int(_list.size()));
for (auto &existing : _proxyRotationPreferredIndices) {
if (existing >= index) {
++existing;
}
}
_list.insert(begin(_list) + index, std::move(value));
}
bool SettingsProxy::removeFromList(const MTP::ProxyData &value) {
const auto i = ranges::find(_list, value);
if (i == end(_list)) {
return false;
}
const auto index = int(i - begin(_list));
_list.erase(i);
for (auto &existing : _proxyRotationPreferredIndices) {
if (existing > index) {
--existing;
}
}
_proxyRotationPreferredIndices.erase(
std::remove(
begin(_proxyRotationPreferredIndices),
end(_proxyRotationPreferredIndices),
index),
end(_proxyRotationPreferredIndices));
return true;
}
bool SettingsProxy::replaceInList(
const MTP::ProxyData &was,
MTP::ProxyData value) {
const auto i = ranges::find(_list, was);
if (i == end(_list)) {
return false;
}
*i = std::move(value);
return true;
}
int SettingsProxy::indexInList(const MTP::ProxyData &value) const {
const auto i = ranges::find(_list, value);
return (i == end(_list)) ? -1 : int(i - begin(_list));
}
rpl::producer<> SettingsProxy::connectionTypeValue() const { rpl::producer<> SettingsProxy::connectionTypeValue() const {
return _connectionTypeChanges.events_starting_with({}); return _connectionTypeChanges.events_starting_with({});
} }
+32 -1
Ver Arquivo
@@ -9,10 +9,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_proxy_data.h" #include "mtproto/mtproto_proxy_data.h"
#include <array>
namespace Core { namespace Core {
class SettingsProxy final { class SettingsProxy final {
public: public:
static constexpr auto kProxyRotationTimeouts = std::array{
5,
10,
15,
30,
60,
};
static constexpr auto kDefaultProxyRotationTimeout = 10;
SettingsProxy(); SettingsProxy();
[[nodiscard]] bool isEnabled() const; [[nodiscard]] bool isEnabled() const;
@@ -29,6 +40,12 @@ public:
[[nodiscard]] bool useProxyForCalls() const; [[nodiscard]] bool useProxyForCalls() const;
void setUseProxyForCalls(bool value); void setUseProxyForCalls(bool value);
[[nodiscard]] bool proxyRotationEnabled() const;
void setProxyRotationEnabled(bool value);
[[nodiscard]] int proxyRotationTimeout() const;
void setProxyRotationTimeout(int value);
[[nodiscard]] MTP::ProxyData::Settings settings() const; [[nodiscard]] MTP::ProxyData::Settings settings() const;
void setSettings(MTP::ProxyData::Settings value); void setSettings(MTP::ProxyData::Settings value);
@@ -38,8 +55,20 @@ public:
[[nodiscard]] bool checkIpWarningShown() const; [[nodiscard]] bool checkIpWarningShown() const;
void setCheckIpWarningShown(bool value); void setCheckIpWarningShown(bool value);
[[nodiscard]] const std::vector<int> &proxyRotationPreferredIndices() const;
void setProxyRotationPreferredIndices(std::vector<int> value);
[[nodiscard]] bool promoteProxyRotationPreferredIndex(int index);
[[nodiscard]] const std::vector<MTP::ProxyData> &list() const; [[nodiscard]] const std::vector<MTP::ProxyData> &list() const;
[[nodiscard]] std::vector<MTP::ProxyData> &list(); [[nodiscard]] std::vector<MTP::ProxyData> &list();
void setList(std::vector<MTP::ProxyData> value);
void addToList(MTP::ProxyData value);
void insertToList(int index, MTP::ProxyData value);
[[nodiscard]] bool removeFromList(const MTP::ProxyData &value);
[[nodiscard]] bool replaceInList(
const MTP::ProxyData &was,
MTP::ProxyData value);
[[nodiscard]] int indexInList(const MTP::ProxyData &value) const;
[[nodiscard]] QByteArray serialize() const; [[nodiscard]] QByteArray serialize() const;
bool setFromSerialized(const QByteArray &serialized); bool setFromSerialized(const QByteArray &serialized);
@@ -47,14 +76,16 @@ public:
private: private:
bool _tryIPv6 = false; bool _tryIPv6 = false;
bool _useProxyForCalls = false; bool _useProxyForCalls = false;
bool _proxyRotationEnabled = false;
bool _checkIpWarningShown = false; bool _checkIpWarningShown = false;
int _proxyRotationTimeout = kDefaultProxyRotationTimeout;
MTP::ProxyData::Settings _settings = MTP::ProxyData::Settings::System; MTP::ProxyData::Settings _settings = MTP::ProxyData::Settings::System;
MTP::ProxyData _selected; MTP::ProxyData _selected;
std::vector<MTP::ProxyData> _list; std::vector<MTP::ProxyData> _list;
std::vector<int> _proxyRotationPreferredIndices;
rpl::event_stream<> _connectionTypeChanges; rpl::event_stream<> _connectionTypeChanges;
}; };
} // namespace Core } // namespace Core
@@ -0,0 +1,359 @@
/*
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 "core/proxy_rotation_manager.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "mtproto/facade.h"
#include <algorithm>
namespace Core {
namespace {
constexpr auto kProxyRotationCheckInterval = 2 * crl::time(1000);
constexpr auto kProxyRotationCheckLifetime = 20 * crl::time(1000);
constexpr auto kProxyRotationMaxActiveChecks = 10;
} // namespace
ProxyRotationManager::ProxyRotationManager()
: _checkTimer([=] { runChecks(); })
, _switchTimer([=] { switchTimerDone(); }) {
App().domain().accountsChanges(
) | rpl::on_next([=] {
stopChecking();
reevaluate();
}, _lifetime);
}
void ProxyRotationManager::settingsChanged() {
stopChecking();
pruneRemovedEntries();
reevaluate();
}
void ProxyRotationManager::handleConnectionStateChanged(
not_null<Main::Account*> account,
int32 state) {
(void)account;
(void)state;
reevaluate();
}
bool ProxyRotationManager::shouldObserve() const {
const auto &settings = App().settings().proxy();
return settings.isEnabled()
&& settings.selected()
&& settings.proxyRotationEnabled()
&& (settings.list().size() > 1);
}
std::vector<not_null<Main::Account*>> ProxyRotationManager::productionAccounts() const {
auto result = std::vector<not_null<Main::Account*>>();
for (const auto &entry : App().domain().accounts()) {
const auto account = entry.account.get();
if (!account->sessionExists() || account->mtp().isTestMode()) {
continue;
}
result.push_back(account);
}
return result;
}
not_null<Main::Account*> ProxyRotationManager::accountForChecks() const {
if (App().someSessionExists()
&& App().activeAccount().sessionExists()
&& !App().activeAccount().mtp().isTestMode()) {
return &App().activeAccount();
}
const auto accounts = productionAccounts();
Expects(!accounts.empty());
return accounts.front();
}
auto ProxyRotationManager::find(
const MTP::ProxyData &proxy) -> Entry* {
const auto i = ranges::find(
_entries,
proxy,
[](const Entry &entry) { return entry.proxy; });
return (i == end(_entries)) ? nullptr : &*i;
}
auto ProxyRotationManager::ensure(
const MTP::ProxyData &proxy) -> Entry& {
if (const auto entry = find(proxy)) {
return *entry;
}
_entries.push_back({ .proxy = proxy });
return _entries.back();
}
void ProxyRotationManager::reevaluate() {
if (!shouldObserve()) {
stopChecking();
return;
}
const auto accounts = productionAccounts();
if (accounts.empty()) {
stopChecking();
return;
}
const auto stateProj = [](not_null<Main::Account*> account) {
return account->mtp().dcstate();
};
if (ranges::contains(accounts, MTP::ConnectedState, stateProj)) {
stopChecking();
return;
}
startChecking();
}
void ProxyRotationManager::startChecking() {
if (_checking) {
return;
}
_checking = true;
_waitingToSwitch = false;
_switchStartedAt = crl::now();
updateProbeOrder();
runChecks();
const auto timeout = App().settings().proxy().proxyRotationTimeout();
_switchTimer.callOnce(timeout * crl::time(1000));
}
void ProxyRotationManager::stopChecking() {
_checkTimer.cancel();
_switchTimer.cancel();
_checking = false;
_waitingToSwitch = false;
_switchStartedAt = 0;
_probeOrder.clear();
_nextCheckIndex = 0;
clearPendingChecks();
}
void ProxyRotationManager::pruneRemovedEntries() {
const auto &settings = App().settings().proxy();
_entries.erase(
std::remove_if(begin(_entries), end(_entries), [&](const Entry &entry) {
return (settings.indexInList(entry.proxy) < 0);
}),
end(_entries));
}
void ProxyRotationManager::updateProbeOrder() {
const auto &settings = App().settings().proxy();
const auto currentIndex = settings.indexInList(settings.selected());
_probeOrder.clear();
_probeOrder.reserve(settings.list().size());
for (const auto index : settings.proxyRotationPreferredIndices()) {
if (index == currentIndex) {
continue;
}
_probeOrder.push_back(index);
}
for (auto i = 0, count = int(settings.list().size()); i != count; ++i) {
if (i == currentIndex || ranges::contains(_probeOrder, i)) {
continue;
}
_probeOrder.push_back(i);
}
_nextCheckIndex = 0;
}
void ProxyRotationManager::continueChecking(crl::time delay) {
if (!_checking) {
return;
}
if (_checkTimer.isActive()) {
_checkTimer.cancel();
}
_checkTimer.callOnce(delay);
}
void ProxyRotationManager::runChecks() {
if (!_checking) {
return;
}
if (!shouldObserve()) {
stopChecking();
return;
}
const auto accounts = productionAccounts();
if (accounts.empty()
|| ranges::contains(
accounts,
MTP::ConnectedState,
[](not_null<Main::Account*> account) {
return account->mtp().dcstate();
})) {
stopChecking();
return;
}
pruneExpiredChecks();
startNextCheck();
continueChecking(kProxyRotationCheckInterval);
}
void ProxyRotationManager::pruneExpiredChecks() {
const auto now = crl::now();
for (auto &entry : _entries) {
if (!entry.checking
|| (now - entry.startedAt < kProxyRotationCheckLifetime)) {
continue;
}
MTP::ResetProxyCheckers(entry.v4, entry.v6);
entry.checking = false;
entry.startedAt = 0;
}
}
void ProxyRotationManager::startNextCheck() {
if (_probeOrder.empty()) {
return;
}
if (ranges::count(_entries, true, &Entry::checking)
>= kProxyRotationMaxActiveChecks) {
return;
}
const auto &settings = App().settings().proxy();
auto attemptsLeft = int(_probeOrder.size());
while (attemptsLeft-- > 0) {
if (_nextCheckIndex >= int(_probeOrder.size())) {
_nextCheckIndex = 0;
}
const auto listIndex = _probeOrder[_nextCheckIndex++];
if (listIndex < 0 || listIndex >= int(settings.list().size())) {
continue;
}
const auto &proxy = settings.list()[listIndex];
auto &entry = ensure(proxy);
if (entry.checking) {
continue;
}
entry.checking = true;
entry.startedAt = crl::now();
MTP::StartProxyCheck(
&accountForChecks()->mtp(),
proxy,
settings.tryIPv6(),
entry.v4,
entry.v6,
[=](MTP::details::AbstractConnection *raw, int ping) {
checkDone(proxy, raw, ping);
},
[=](MTP::details::AbstractConnection *raw) {
checkFailed(proxy, raw);
});
break;
}
}
void ProxyRotationManager::switchTimerDone() {
if (!_checking || !shouldSwitchToAvailable()) {
return;
}
_waitingToSwitch = !switchToAvailable();
}
void ProxyRotationManager::clearPendingChecks() {
for (auto &entry : _entries) {
MTP::ResetProxyCheckers(entry.v4, entry.v6);
entry.checking = false;
entry.startedAt = 0;
}
}
void ProxyRotationManager::checkDone(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw,
int ping) {
const auto entry = find(proxy);
if (!entry
|| !entry->checking
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
return;
}
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
MTP::ResetProxyCheckers(entry->v4, entry->v6);
entry->checking = false;
entry->startedAt = 0;
entry->availableAt = crl::now();
const auto proxySettings = &App().settings().proxy();
if (const auto index = proxySettings->indexInList(proxy); index >= 0) {
if (proxySettings->promoteProxyRotationPreferredIndex(index)) {
App().saveSettingsDelayed();
}
}
updateProbeOrder();
if (_waitingToSwitch && shouldSwitchToAvailable()) {
_waitingToSwitch = !switchToAvailable();
}
}
void ProxyRotationManager::checkFailed(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw) {
const auto entry = find(proxy);
if (!entry
|| !entry->checking
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
return;
}
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
if (MTP::HasProxyCheckers(entry->v4, entry->v6)) {
return;
}
entry->checking = false;
entry->startedAt = 0;
}
bool ProxyRotationManager::switchToAvailable() {
if (!_checking) {
return false;
}
const auto &settings = App().settings().proxy();
for (const auto index : _probeOrder) {
if (index < 0 || index >= int(settings.list().size())) {
continue;
}
const auto &proxy = settings.list()[index];
const auto entry = find(proxy);
if (!entry || entry->checking || !entry->availableAt) {
continue;
}
if (entry->availableAt < _switchStartedAt) {
continue;
}
_waitingToSwitch = false;
App().setCurrentProxy(proxy, MTP::ProxyData::Settings::Enabled);
App().saveSettingsDelayed();
return true;
}
return false;
}
bool ProxyRotationManager::shouldSwitchToAvailable() const {
if (!_checking || !shouldObserve()) {
return false;
}
const auto accounts = productionAccounts();
return !accounts.empty()
&& !ranges::contains(
accounts,
MTP::ConnectedState,
[](not_null<Main::Account*> account) {
return account->mtp().dcstate();
});
}
} // namespace Core
@@ -0,0 +1,80 @@
/*
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"
#include "mtproto/proxy_check.h"
#include <rpl/lifetime.h>
#include <vector>
namespace Main {
class Account;
} // namespace Main
namespace Core {
class ProxyRotationManager final {
public:
ProxyRotationManager();
void settingsChanged();
void handleConnectionStateChanged(
not_null<Main::Account*> account,
int32 state);
private:
struct Entry {
MTP::ProxyData proxy;
MTP::ProxyCheckConnection v4;
MTP::ProxyCheckConnection v6;
bool checking = false;
crl::time startedAt = 0;
crl::time availableAt = 0;
};
[[nodiscard]] bool shouldObserve() const;
[[nodiscard]] std::vector<not_null<Main::Account*>> productionAccounts() const;
[[nodiscard]] not_null<Main::Account*> accountForChecks() const;
[[nodiscard]] Entry *find(const MTP::ProxyData &proxy);
[[nodiscard]] Entry &ensure(const MTP::ProxyData &proxy);
void reevaluate();
void startChecking();
void stopChecking();
void pruneRemovedEntries();
void updateProbeOrder();
void continueChecking(crl::time delay);
void runChecks();
void pruneExpiredChecks();
void startNextCheck();
void switchTimerDone();
void clearPendingChecks();
void checkDone(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw,
int ping);
void checkFailed(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw);
[[nodiscard]] bool switchToAvailable();
[[nodiscard]] bool shouldSwitchToAvailable() const;
base::Timer _checkTimer;
base::Timer _switchTimer;
std::vector<Entry> _entries;
std::vector<int> _probeOrder;
int _nextCheckIndex = 0;
bool _checking = false;
bool _waitingToSwitch = false;
crl::time _switchStartedAt = 0;
rpl::lifetime _lifetime;
};
} // namespace Core
+3 -3
Ver Arquivo
@@ -24,7 +24,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs; constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 6007006; constexpr auto AppVersion = 6007007;
constexpr auto AppVersionStr = "6.7.6"; constexpr auto AppVersionStr = "6.7.7";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
+72 -14
Ver Arquivo
@@ -88,6 +88,16 @@ void UpdateStickerSetIdentifier(
}); });
} }
[[nodiscard]] int ResolveAttributeVsTranscodeQuality(
int attributesQuality,
int transcodeMax) {
return (transcodeMax > 0
&& (attributesQuality < transcodeMax
|| attributesQuality > transcodeMax * 1.5))
? transcodeMax
: attributesQuality;
}
} // namespace } // namespace
QString FileNameUnsafe( QString FileNameUnsafe(
@@ -547,8 +557,7 @@ void DocumentData::setVideoQualities(
return document->isVideoFile() return document->isVideoFile()
&& !document->dimensions.isEmpty() && !document->dimensions.isEmpty()
&& !document->inappPlaybackFailed() && !document->inappPlaybackFailed()
&& document->useStreamingLoader() && document->useStreamingLoader();
&& document->canBeStreamed();
}; };
ranges::sort( ranges::sort(
qualities, qualities,
@@ -578,18 +587,58 @@ void DocumentData::setVideoQualities(
} }
qualities.erase(qualities.begin() + count, qualities.end()); qualities.erase(qualities.begin() + count, qualities.end());
if (!qualities.empty()) { if (!qualities.empty()) {
if (const auto mine = resolveVideoQuality()) { auto transcodeMax = 0;
if (mine > qualities.front()->resolveVideoQuality()) { for (const auto &quality : qualities) {
qualities.insert(begin(qualities), this); const auto qres = quality->resolveVideoQuality();
if (qres > transcodeMax) {
transcodeMax = qres;
} }
} }
const auto attributesSize = isVideoFile() ? dimensions : QSize();
const auto attributesQuality = attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
auto mine = ResolveAttributeVsTranscodeQuality(
attributesQuality,
transcodeMax);
if (mine) {
qualities.insert(begin(qualities), this);
}
} }
data->qualities = std::move(qualities); data->qualities = std::move(qualities);
} }
int DocumentData::resolveVideoQuality() const { int DocumentData::resolveVideoQuality() const {
const auto size = isVideoFile() ? dimensions : QSize(); if (const auto data = video()) {
return size.isEmpty() ? 0 : std::min(size.width(), size.height()); if (!data->realVideoSize.isEmpty()) {
const auto size = data->realVideoSize;
return std::min(size.width(), size.height());
}
const auto attributesSize = isVideoFile() ? dimensions : QSize();
const auto attributesQuality = attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
if (!data->qualities.empty()) {
auto transcodeMax = 0;
for (const auto &quality : data->qualities) {
if (quality != this) {
const auto qres = quality->resolveVideoQuality();
if (qres > transcodeMax) {
transcodeMax = qres;
}
}
}
if (transcodeMax > 0) {
return ResolveAttributeVsTranscodeQuality(
attributesQuality,
transcodeMax);
}
}
}
const auto attributesSize = isVideoFile() ? dimensions : QSize();
return attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
} }
auto DocumentData::resolveQualities(HistoryItem *context) const auto DocumentData::resolveQualities(HistoryItem *context) const
@@ -611,19 +660,28 @@ not_null<DocumentData*> DocumentData::chooseQuality(
return this; return this;
} }
const auto height = int(request.height); const auto height = int(request.height);
auto closest = this; if (request.original) {
auto closestAbs = std::abs(height - resolveVideoQuality()); return this;
auto closestSize = size; }
auto closest = (DocumentData*)nullptr;
auto closestAbs = -1;
auto closestSize = -1;
for (const auto &quality : list) { for (const auto &quality : list) {
const auto abs = std::abs(height - quality->resolveVideoQuality()); const auto qres = quality->resolveVideoQuality();
if (abs < closestAbs const auto abs = std::abs(height - qres);
|| (abs == closestAbs && quality->size < closestSize)) { if (!closest
|| abs < closestAbs
|| (abs == closestAbs && (quality->size < closestSize
|| (closest == this && quality != this)))) {
closest = quality; closest = quality;
closestAbs = abs; closestAbs = abs;
closestSize = quality->size; closestSize = quality->size;
} }
} }
return closest;
return closest ? closest : this;
} }
void DocumentData::validateLottieSticker() { void DocumentData::validateLottieSticker() {
+1
Ver Arquivo
@@ -99,6 +99,7 @@ struct VoiceData : public DocumentAdditionalData {
struct VideoData : public DocumentAdditionalData { struct VideoData : public DocumentAdditionalData {
QString codec; QString codec;
std::vector<not_null<DocumentData*>> qualities; std::vector<not_null<DocumentData*>> qualities;
QSize realVideoSize;
}; };
using RoundData = VoiceData; using RoundData = VoiceData;
@@ -200,7 +200,7 @@ Image *DocumentMedia::goodThumbnail() const {
} }
void DocumentMedia::setGoodThumbnail(QImage thumbnail) { void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
if (!(_flags & Flag::GoodThumbnailWanted)) { if (!(_flags & Flag::GoodThumbnailWanted) || thumbnail.isNull()) {
return; return;
} }
_goodThumbnail = std::make_unique<Image>(std::move(thumbnail)); _goodThumbnail = std::make_unique<Image>(std::move(thumbnail));
+18 -11
Ver Arquivo
@@ -4174,6 +4174,21 @@ void Session::webpageApplyFields(
auto iv = (data.vcached_page() && !IgnoreIv(type)) auto iv = (data.vcached_page() && !IgnoreIv(type))
? std::make_unique<Iv::Data>(data, *data.vcached_page()) ? std::make_unique<Iv::Data>(data, *data.vcached_page())
: nullptr; : nullptr;
const auto resolvedPhoto = story
? story->photo()
: photo
? processPhoto(*photo).get()
: nullptr;
const auto resolvedDocument = story
? story->document()
: document
? processDocument(*document).get()
: lookupThemeDocument();
const auto photoIsVideoCover = data.is_video_cover_photo()
|| (resolvedDocument
&& resolvedPhoto
&& resolvedDocument->isVideoFile()
&& !resolvedDocument->hasThumbnail());
webpageApplyFields( webpageApplyFields(
page, page,
type, type,
@@ -4183,16 +4198,8 @@ void Session::webpageApplyFields(
qs(data.vtitle().value_or_empty()), qs(data.vtitle().value_or_empty()),
(story ? story->caption() : description), (story ? story->caption() : description),
storyId, storyId,
(story resolvedPhoto,
? story->photo() resolvedDocument,
: photo
? processPhoto(*photo).get()
: nullptr),
(story
? story->document()
: document
? processDocument(*document).get()
: lookupThemeDocument()),
WebPageCollage(this, data), WebPageCollage(this, data),
std::move(iv), std::move(iv),
lookupStickerSet(), lookupStickerSet(),
@@ -4201,7 +4208,7 @@ void Session::webpageApplyFields(
data.vduration().value_or_empty(), data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()), qs(data.vauthor().value_or_empty()),
data.is_has_large_media(), data.is_has_large_media(),
data.is_video_cover_photo(), photoIsVideoCover,
pendingTill); pendingTill);
} }
@@ -18,8 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kShortPollTimeout = 30 * crl::time(1000);
const TodoListItem *ItemById(const std::vector<TodoListItem> &list, int id) { const TodoListItem *ItemById(const std::vector<TodoListItem> &list, int id) {
const auto i = ranges::find(list, id, &TodoListItem::id); const auto i = ranges::find(list, id, &TodoListItem::id);
return (i != end(list)) ? &*i : nullptr; return (i != end(list)) ? &*i : nullptr;
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_indexed_list.h"
#include "base/unixtime.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_folder.h" #include "data/data_folder.h"
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h" #include "mainwidget.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "history/history.h" #include "history/history.h"
@@ -320,6 +322,33 @@ const Ui::Text::String &Entry::chatListNameText() const {
return _chatListNameText; return _chatListNameText;
} }
DateText ResolveDateText(
DateTextCache &cache,
TimeId date,
crl::time now) {
static crl::time LastNow = 0;
static int LastTodaySerial = 0;
if (!now || LastNow != now) {
LastNow = now;
LastTodaySerial = int(QDate::currentDate().toJulianDay());
}
if (cache.messageTimeId != date
|| cache.todaySerial != LastTodaySerial) {
const auto qdt = base::unixtime::parse(date);
cache.text = Ui::FormatDialogsDate(qdt);
cache.width = st::dialogsDateFont->width(cache.text);
cache.messageTimeId = date;
cache.todaySerial = LastTodaySerial;
}
return { cache.text, cache.width };
}
DateText Entry::chatListTimestampText(
TimeId date,
crl::time now) const {
return ResolveDateText(_chatListDateCache, date, now);
}
void Entry::setChatListExistence(bool exists) { void Entry::setChatListExistence(bool exists) {
if (exists && _sortKeyInChatList) { if (exists && _sortKeyInChatList) {
owner().refreshChatListEntry(this); owner().refreshChatListEntry(this);
+21
Ver Arquivo
@@ -52,6 +52,23 @@ class MainList;
CountInBadge count = CountInBadge::Default, CountInBadge count = CountInBadge::Default,
IncludeInBadge include = IncludeInBadge::Default); IncludeInBadge include = IncludeInBadge::Default);
struct DateTextCache {
QString text;
TimeId messageTimeId = 0;
int todaySerial = 0;
int width = 0;
};
struct DateText {
const QString &text;
int width = 0;
};
[[nodiscard]] DateText ResolveDateText(
DateTextCache &cache,
TimeId date,
crl::time now);
class Entry : public base::has_weak_ptr { class Entry : public base::has_weak_ptr {
public: public:
enum class Type : uchar { enum class Type : uchar {
@@ -154,6 +171,9 @@ public:
} }
[[nodiscard]] const Ui::Text::String &chatListNameText() const; [[nodiscard]] const Ui::Text::String &chatListNameText() const;
[[nodiscard]] DateText chatListTimestampText(
TimeId date,
crl::time now) const;
[[nodiscard]] Ui::PeerBadge &chatListPeerBadge() const { [[nodiscard]] Ui::PeerBadge &chatListPeerBadge() const {
return _chatListPeerBadge; return _chatListPeerBadge;
} }
@@ -194,6 +214,7 @@ private:
mutable Ui::PeerBadge _chatListPeerBadge; mutable Ui::PeerBadge _chatListPeerBadge;
mutable Ui::Text::String _chatListNameText; mutable Ui::Text::String _chatListNameText;
mutable int _chatListNameVersion = 0; mutable int _chatListNameVersion = 0;
mutable DateTextCache _chatListDateCache;
TimeId _timeId = 0; TimeId _timeId = 0;
Flags _flags; Flags _flags;
@@ -764,4 +764,10 @@ const Ui::Text::String &FakeRow::name() const {
return _name; return _name;
} }
DateText FakeRow::dateText(
TimeId date,
crl::time now) const {
return ResolveDateText(_dateCache, date, now);
}
} // namespace Dialogs } // namespace Dialogs
+5
Ver Arquivo
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text.h" #include "ui/text/text.h"
#include "ui/unread_badge.h" #include "ui/unread_badge.h"
#include "ui/userpic_view.h" #include "ui/userpic_view.h"
#include "dialogs/dialogs_entry.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "dialogs/ui/dialogs_message_view.h" #include "dialogs/ui/dialogs_message_view.h"
@@ -239,6 +240,9 @@ public:
return _badge; return _badge;
} }
[[nodiscard]] const Ui::Text::String &name() const; [[nodiscard]] const Ui::Text::String &name() const;
[[nodiscard]] DateText dateText(
TimeId date,
crl::time now) const;
void invalidateTopic(); void invalidateTopic();
@@ -252,6 +256,7 @@ private:
mutable Ui::MessageView _itemView; mutable Ui::MessageView _itemView;
mutable Ui::PeerBadge _badge; mutable Ui::PeerBadge _badge;
mutable Ui::Text::String _name; mutable Ui::Text::String _name;
mutable DateTextCache _dateCache;
}; };
+29 -21
Ver Arquivo
@@ -31,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "history/view/history_view_send_action.h" #include "history/view/history_view_send_action.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_icon.h" #include "lottie/lottie_icon.h"
#include "main/main_session.h" #include "main/main_session.h"
@@ -90,8 +89,11 @@ void PaintRowTopRight(
QPainter &p, QPainter &p,
const QString &text, const QString &text,
QRect &rectForName, QRect &rectForName,
const PaintContext &context) { const PaintContext &context,
const auto width = st::dialogsDateFont->width(text); int precomputedWidth = -1) {
const auto width = (precomputedWidth >= 0)
? precomputedWidth
: st::dialogsDateFont->width(text);
rectForName.setWidth(rectForName.width() - width - st::dialogsDateSkip); rectForName.setWidth(rectForName.width() - width - st::dialogsDateSkip);
p.setFont(st::dialogsDateFont); p.setFont(st::dialogsDateFont);
p.setPen(context.active p.setPen(context.active
@@ -388,6 +390,19 @@ enum class Flag {
}; };
inline constexpr bool is_flag_type(Flag) { return true; } inline constexpr bool is_flag_type(Flag) { return true; }
void PaintDialogDate(
QPainter &p,
not_null<const Entry*> entry,
const FakeRow *fakeRow,
TimeId date,
QRect &rectForName,
const PaintContext &context) {
const auto resolved = fakeRow
? fakeRow->dateText(date, context.now)
: entry->chatListTimestampText(date, context.now);
PaintRowTopRight(p, resolved.text, rectForName, context, resolved.width);
}
template <typename PaintItemCallback> template <typename PaintItemCallback>
void PaintRow( void PaintRow(
Painter &p, Painter &p,
@@ -402,8 +417,9 @@ void PaintRow(
const HiddenSenderInfo *hiddenSenderInfo, const HiddenSenderInfo *hiddenSenderInfo,
HistoryItem *item, HistoryItem *item,
const Data::Draft *draft, const Data::Draft *draft,
QDateTime date, TimeId date,
const PaintContext &context, const PaintContext &context,
const FakeRow *fakeRow,
BadgesState badgesState, BadgesState badgesState,
base::flags<Flag> flags, base::flags<Flag> flags,
PaintItemCallback &&paintItemCallback) { PaintItemCallback &&paintItemCallback) {
@@ -597,8 +613,7 @@ void PaintRow(
|| (supportMode || (supportMode
&& entry->session().supportHelper().isOccupiedBySomeone(history))) { && entry->session().supportHelper().isOccupiedBySomeone(history))) {
if (!promoted) { if (!promoted) {
const auto dateString = Ui::FormatDialogsDate(date); PaintDialogDate(p, entry, fakeRow, date, rectForName, context);
PaintRowTopRight(p, dateString, rectForName, context);
} }
auto availableWidth = namewidth; auto availableWidth = namewidth;
@@ -729,8 +744,7 @@ void PaintRow(
} }
} else if (!item->isEmpty()) { } else if (!item->isEmpty()) {
if ((thread || sublist) && !promoted) { if ((thread || sublist) && !promoted) {
const auto dateString = Ui::FormatDialogsDate(date); PaintDialogDate(p, entry, fakeRow, date, rectForName, context);
PaintRowTopRight(p, dateString, rectForName, context);
} }
paintItemCallback(nameleft, namewidth); paintItemCallback(nameleft, namewidth);
@@ -1083,18 +1097,10 @@ void RowPainter::Paint(
} }
return nullptr; return nullptr;
}(); }();
const auto displayDate = [&] { const auto displayDate = [&]() -> TimeId {
if (item) { const auto itemDate = item ? item->date() : TimeId(0);
if (cloudDraft) { const auto draftDate = cloudDraft ? cloudDraft->date : TimeId(0);
return (item->date() > cloudDraft->date) return std::max(itemDate, draftDate);
? ItemDateTime(item)
: base::unixtime::parse(cloudDraft->date);
}
return ItemDateTime(item);
}
return cloudDraft
? base::unixtime::parse(cloudDraft->date)
: QDateTime();
}(); }();
const auto displayPinnedIcon = badgesState.empty() const auto displayPinnedIcon = badgesState.empty()
&& entry->isPinnedDialog(context.filter) && entry->isPinnedDialog(context.filter)
@@ -1194,6 +1200,7 @@ void RowPainter::Paint(
cloudDraft, cloudDraft,
displayDate, displayDate,
context, context,
nullptr,
badgesState, badgesState,
flags, flags,
paintItemCallback); paintItemCallback);
@@ -1303,8 +1310,9 @@ void RowPainter::Paint(
hiddenSenderInfo, hiddenSenderInfo,
item, item,
cloudDraft, cloudDraft,
ItemDateTime(item), item->date(),
context, context,
row,
badgesState, badgesState,
flags, flags,
paintItemCallback); paintItemCallback);
+31 -4
Ver Arquivo
@@ -351,6 +351,7 @@ ColorPicker::ColorPicker(
button->show(); button->show();
} }
const auto setToolRequest = [=](Brush::Tool tool) { const auto setToolRequest = [=](Brush::Tool tool) {
_toolClicks.fire({});
setTool(tool); setTool(tool);
}; };
if (_toolButtons.size() >= 5) { if (_toolButtons.size() >= 5) {
@@ -566,10 +567,28 @@ void ColorPicker::setTool(Brush::Tool tool) {
} }
void ColorPicker::storeCurrentBrush() { void ColorPicker::storeCurrentBrush() {
if (_toolSelectionSuppressed) {
return;
}
NormalizeBrushColor(_brush); NormalizeBrushColor(_brush);
_toolBrushes[ToolIndex(_brush.tool)] = _brush; _toolBrushes[ToolIndex(_brush.tool)] = _brush;
} }
void ColorPicker::setColor(const QColor &color) {
_brush.color = color;
updateColorButtonColor(color, true);
if (_paletteVisible) {
rebuildPalette();
} else {
_colorButton->update();
}
}
void ColorPicker::setToolSelectionVisible(bool visible) {
_toolSelectionSuppressed = !visible;
_toolSelection->setVisible(visible);
}
void ColorPicker::updateColorButtonColor(const QColor &color, bool animated) { void ColorPicker::updateColorButtonColor(const QColor &color, bool animated) {
const auto hasValid = _colorButtonFrom.isValid() && _colorButtonTo.isValid(); const auto hasValid = _colorButtonFrom.isValid() && _colorButtonTo.isValid();
const auto from = hasValid ? colorButtonColor() : color; const auto from = hasValid ? colorButtonColor() : color;
@@ -632,11 +651,14 @@ void ColorPicker::setVisible(bool visible) {
_paletteWrap->setVisible(visible && _paletteVisible); _paletteWrap->setVisible(visible && _paletteVisible);
_sizeControlHoverArea->setVisible(visible); _sizeControlHoverArea->setVisible(visible);
_sizeControl->setVisible(visible); _sizeControl->setVisible(visible);
_toolSelection->setVisible(visible && !_paletteVisible); const auto showTools = visible
&& !_paletteVisible
&& !_toolSelectionSuppressed;
_toolSelection->setVisible(showTools);
for (const auto &button : _toolButtons) { for (const auto &button : _toolButtons) {
button->setVisible(visible && !_paletteVisible); button->setVisible(visible && !_paletteVisible);
} }
if (visible && !_paletteVisible) { if (showTools) {
updateToolSelection(false); updateToolSelection(false);
} }
} }
@@ -645,6 +667,10 @@ rpl::producer<Brush> ColorPicker::saveBrushRequests() const {
return _saveBrushRequests.events_starting_with_copy(_brush); return _saveBrushRequests.events_starting_with_copy(_brush);
} }
rpl::producer<> ColorPicker::toolClicks() const {
return _toolClicks.events();
}
bool ColorPicker::preventHandleKeyPress() const { bool ColorPicker::preventHandleKeyPress() const {
return _sizeControl->isVisible() return _sizeControl->isVisible()
&& (_sizeControlAnimation.animating() || _sizeDown.pressed); && (_sizeControlAnimation.animating() || _sizeDown.pressed);
@@ -785,13 +811,14 @@ void ColorPicker::setPaletteVisible(bool visible) {
_paletteVisible = visible; _paletteVisible = visible;
_paletteWrap->setVisible(visible); _paletteWrap->setVisible(visible);
_colorButton->setVisible(!visible); _colorButton->setVisible(!visible);
_toolSelection->setVisible(!visible); const auto showTools = !visible && !_toolSelectionSuppressed;
_toolSelection->setVisible(showTools);
for (const auto &button : _toolButtons) { for (const auto &button : _toolButtons) {
button->setVisible(!visible); button->setVisible(!visible);
} }
if (visible) { if (visible) {
rebuildPalette(); rebuildPalette();
} else { } else if (showTools) {
updateToolSelection(false); updateToolSelection(false);
} }
} }
+5
Ver Arquivo
@@ -30,9 +30,12 @@ public:
void moveLine(const QPoint &position); void moveLine(const QPoint &position);
void setCanvasRect(const QRect &rect); void setCanvasRect(const QRect &rect);
void setVisible(bool visible); void setVisible(bool visible);
void setColor(const QColor &color);
void setToolSelectionVisible(bool visible);
bool preventHandleKeyPress() const; bool preventHandleKeyPress() const;
rpl::producer<Brush> saveBrushRequests() const; rpl::producer<Brush> saveBrushRequests() const;
rpl::producer<> toolClicks() const;
private: private:
void paintSizeControl(QPainter &p); void paintSizeControl(QPainter &p);
@@ -75,6 +78,7 @@ private:
int y = 0; int y = 0;
bool pressed = false; bool pressed = false;
} _sizeDown; } _sizeDown;
bool _toolSelectionSuppressed = false;
bool _sizeHoverAreaHovered = false; bool _sizeHoverAreaHovered = false;
bool _sizeControlHovered = false; bool _sizeControlHovered = false;
bool _sizeControlExpanded = false; bool _sizeControlExpanded = false;
@@ -94,6 +98,7 @@ private:
Ui::Animations::Simple _toolSelectionAnimation; Ui::Animations::Simple _toolSelectionAnimation;
rpl::event_stream<Brush> _saveBrushRequests; rpl::event_stream<Brush> _saveBrushRequests;
rpl::event_stream<> _toolClicks;
std::vector<base::unique_qptr<Ui::ColorSample>> _paletteButtons; std::vector<base::unique_qptr<Ui::ColorSample>> _paletteButtons;
base::unique_qptr<Ui::AbstractButton> _palettePlus; base::unique_qptr<Ui::AbstractButton> _palettePlus;
+17
Ver Arquivo
@@ -177,6 +177,10 @@ void Crop::paintFrame(QPainter &p) {
p.save(); p.save();
p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::Antialiasing, true);
p.fillPath(frameShape, st::photoCropPointFg); p.fillPath(frameShape, st::photoCropPointFg);
if (_data.fixedCrop) {
p.restore();
return;
}
{ {
const auto cornerLength = std::min( const auto cornerLength = std::min(
float64(st::photoEditorCropPointSize * 2), float64(st::photoEditorCropPointSize * 2),
@@ -286,6 +290,10 @@ void Crop::convertCropPaintToOriginal() {
} }
void Crop::updateEdges() { void Crop::updateEdges() {
if (_data.fixedCrop) {
_edges.clear();
return;
}
const auto &s = _pointSize; const auto &s = _pointSize;
const auto &m = _edgePointMargins; const auto &m = _edgePointMargins;
const auto &r = _cropPaint; const auto &r = _cropPaint;
@@ -338,6 +346,9 @@ Qt::Edges Crop::mouseState(const QPoint &p) {
} }
void Crop::mousePressEvent(QMouseEvent *e) { void Crop::mousePressEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
computeDownState(e->pos()); computeDownState(e->pos());
if (_down.edge) { if (_down.edge) {
setGridVisible(true, false); setGridVisible(true, false);
@@ -345,6 +356,9 @@ void Crop::mousePressEvent(QMouseEvent *e) {
} }
void Crop::mouseReleaseEvent(QMouseEvent *e) { void Crop::mouseReleaseEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
const auto hadEdge = bool(_down.edge); const auto hadEdge = bool(_down.edge);
if (hadEdge) { if (hadEdge) {
setGridVisible(false, true); setGridVisible(false, true);
@@ -474,6 +488,9 @@ void Crop::performMove(const QPoint &pos) {
} }
void Crop::mouseMoveEvent(QMouseEvent *e) { void Crop::mouseMoveEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
const auto pos = e->pos(); const auto pos = e->pos();
const auto pressedEdge = _down.edge; const auto pressedEdge = _down.edge;
+127 -9
Ver Arquivo
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene_item_canvas.h" #include "editor/scene/scene_item_canvas.h"
#include "editor/scene/scene_item_image.h" #include "editor/scene/scene_item_image.h"
#include "editor/scene/scene_item_sticker.h" #include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_text.h"
#include "editor/scene/scene.h" #include "editor/scene/scene.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_single_player.h" #include "lottie/lottie_single_player.h"
@@ -35,6 +36,9 @@ constexpr auto kMinCanvasZoom = 1.;
constexpr auto kMaxCanvasZoom = 8.; constexpr auto kMaxCanvasZoom = 8.;
constexpr auto kCanvasZoomStep = 1.15; constexpr auto kCanvasZoomStep = 1.15;
constexpr auto kZoomEpsilon = 0.0001; constexpr auto kZoomEpsilon = 0.0001;
constexpr auto kMinItemZoom = 0.1;
constexpr auto kMaxItemZoom = 10.;
constexpr auto kCanvasZoomStepFine = 1.015;
std::shared_ptr<Scene> EnsureScene( std::shared_ptr<Scene> EnsureScene(
PhotoModifications &mods, PhotoModifications &mods,
@@ -54,17 +58,30 @@ Paint::Paint(
PhotoModifications &modifications, PhotoModifications &modifications,
const QSize &imageSize, const QSize &imageSize,
std::shared_ptr<Controllers> controllers, std::shared_ptr<Controllers> controllers,
Fn<QImage(QRect)> blurSource) Fn<QImage(QRect)> blurSource,
bool fixedCrop)
: RpWidget(parent) : RpWidget(parent)
, _controllers(controllers) , _controllers(controllers)
, _scene(EnsureScene(modifications, imageSize)) , _scene(EnsureScene(modifications, imageSize))
, _view(base::make_unique_q<QGraphicsView>(_scene.get(), this)) , _view(base::make_unique_q<QGraphicsView>(_scene.get(), this))
, _viewport(_view->viewport()) , _viewport(_view->viewport())
, _imageSize(imageSize) { , _imageSize(imageSize)
, _fixedCrop(fixedCrop) {
Expects(modifications.paint != nullptr); Expects(modifications.paint != nullptr);
_scene->setBlurSource(std::move(blurSource)); _scene->setBlurSource(std::move(blurSource));
{
constexpr auto kDefaultFontSizeDivisor = 15.;
const auto shortSide = std::min(
imageSize.width(),
imageSize.height());
_scene->setTextDefaults(
QColor(255, 255, 255),
shortSide / kDefaultFontSizeDivisor,
int(TextStyle::Plain));
}
keepResult(); keepResult();
_view->show(); _view->show();
@@ -77,9 +94,17 @@ Paint::Paint(
_viewport->setAttribute(Qt::WA_TranslucentBackground, true); _viewport->setAttribute(Qt::WA_TranslucentBackground, true);
_viewport->installEventFilter(this); _viewport->installEventFilter(this);
_scene->textEditStates(
) | rpl::on_next([=](bool editing) {
_textEditing = editing;
}, lifetime());
// Undo / Redo. // Undo / Redo.
controllers->undoController->performRequestChanges( controllers->undoController->performRequestChanges(
) | rpl::on_next([=](const Undo &command) { ) | rpl::on_next([=](const Undo &command) {
if (_textEditing.current()) {
return;
}
if (command == Undo::Undo) { if (command == Undo::Undo) {
_scene->performUndo(); _scene->performUndo();
} else { } else {
@@ -91,16 +116,22 @@ Paint::Paint(
}, lifetime()); }, lifetime());
controllers->undoController->setCanPerformChanges(rpl::merge( controllers->undoController->setCanPerformChanges(rpl::merge(
_hasUndo.value() | rpl::map([](bool enable) { rpl::combine(
_hasUndo.value(),
_textEditing.value()
) | rpl::map([](bool enable, bool editing) {
return UndoController::EnableRequest{ return UndoController::EnableRequest{
.command = Undo::Undo, .command = Undo::Undo,
.enable = enable, .enable = enable && !editing,
}; };
}), }),
_hasRedo.value() | rpl::map([](bool enable) { rpl::combine(
_hasRedo.value(),
_textEditing.value()
) | rpl::map([](bool enable, bool editing) {
return UndoController::EnableRequest{ return UndoController::EnableRequest{
.command = Undo::Redo, .command = Undo::Redo,
.enable = enable, .enable = enable && !editing,
}; };
}))); })));
@@ -139,6 +170,51 @@ Paint::Paint(
} }
bool Paint::zoomSceneItems(float64 wheelDelta, bool fine) {
if (!wheelDelta) {
return false;
}
const auto step = wheelDelta
/ float64(QWheelEvent::DefaultDeltasPerStep);
const auto base = fine ? kCanvasZoomStepFine : kCanvasZoomStep;
const auto factor = std::pow(base, step);
const auto center = rect::center(_scene->sceneRect());
auto applied = false;
for (const auto &item : _scene->items()) {
const auto raw = item.get();
const auto oldScale = raw->scale();
const auto newScale = std::clamp(
oldScale * factor,
kMinItemZoom,
kMaxItemZoom);
if (std::abs(newScale - oldScale) < kZoomEpsilon) {
continue;
}
const auto ratio = newScale / oldScale;
raw->setScale(newScale);
const auto pos = raw->pos();
raw->setPos(center + (pos - center) * ratio);
applied = true;
}
return applied;
}
void Paint::panSceneItems(QPointF sceneDelta) {
if (sceneDelta.isNull()) {
return;
}
for (const auto &item : _scene->items()) {
item->setPos(item->pos() + sceneDelta);
}
}
QPointF Paint::mapWidgetDeltaToScene(QPoint delta) const {
if (!_view) {
return QPointF(delta);
}
return _view->mapToScene(delta) - _view->mapToScene(QPoint());
}
Paint::~Paint() { Paint::~Paint() {
if (_viewport) { if (_viewport) {
_viewport->removeEventFilter(this); _viewport->removeEventFilter(this);
@@ -234,6 +310,38 @@ void Paint::applyBrush(const Brush &brush) {
brush.tool); brush.tool);
} }
void Paint::createTextItem() {
_scene->createTextAtCenter();
}
void Paint::clearSelection() {
_scene->clearSelection();
}
void Paint::setTextColor(const QColor &color) {
_scene->setTextColor(color);
}
void Paint::setSelectedTextColor(const QColor &color) {
_scene->setSelectedTextColor(color);
}
rpl::producer<QColor> Paint::textColorRequests() const {
return _scene->textColorRequests();
}
rpl::producer<QColor> Paint::textItemSelections() const {
return _scene->textItemSelections();
}
rpl::producer<> Paint::textItemDeselections() const {
return _scene->textItemDeselections();
}
rpl::producer<bool> Paint::textEditStates() const {
return _scene->textEditStates();
}
void Paint::handleMimeData(const QMimeData *data) { void Paint::handleMimeData(const QMimeData *data) {
const auto add = [&](QImage image) { const auto add = [&](QImage image) {
if (image.isNull()) { if (image.isNull()) {
@@ -330,11 +438,18 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) {
} }
if (e->type() == QEvent::Wheel) { if (e->type() == QEvent::Wheel) {
const auto wheel = static_cast<QWheelEvent*>(e); const auto wheel = static_cast<QWheelEvent*>(e);
const auto delta = wheel->angleDelta().y(); const auto raw = wheel->angleDelta();
const auto delta = raw.y() ? raw.y() : raw.x();
if (!delta) { if (!delta) {
return true; return true;
} }
if (_fixedCrop) {
zoomSceneItems(
delta,
wheel->modifiers().testFlag(Qt::ShiftModifier));
return true;
}
const auto step = delta / float64(QWheelEvent::DefaultDeltasPerStep); const auto step = delta / float64(QWheelEvent::DefaultDeltasPerStep);
const auto factor = std::pow(kCanvasZoomStep, step); const auto factor = std::pow(kCanvasZoomStep, step);
const auto newZoom = std::clamp( const auto newZoom = std::clamp(
@@ -363,7 +478,8 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) {
const auto mouse = static_cast<QMouseEvent*>(e); const auto mouse = static_cast<QMouseEvent*>(e);
if (mouse->button() == Qt::MiddleButton) { if (mouse->button() == Qt::MiddleButton) {
_pan = { _pan = {
.active = (_transform.userZoom > kMinCanvasZoom), .active = (_fixedCrop
|| _transform.userZoom > kMinCanvasZoom),
.point = mouse->pos(), .point = mouse->pos(),
}; };
if (_pan.active) { if (_pan.active) {
@@ -378,7 +494,9 @@ bool Paint::eventFilter(QObject *obj, QEvent *e) {
const auto delta = point - _pan.point; const auto delta = point - _pan.point;
_pan.point = point; _pan.point = point;
if (_transform.userZoom > kMinCanvasZoom) { if (_fixedCrop) {
panSceneItems(mapWidgetDeltaToScene(delta));
} else if (_transform.userZoom > kMinCanvasZoom) {
view->horizontalScrollBar()->setValue( view->horizontalScrollBar()->setValue(
view->horizontalScrollBar()->value() - delta.x()); view->horizontalScrollBar()->value() - delta.x());
view->verticalScrollBar()->setValue( view->verticalScrollBar()->setValue(
+18 -1
Ver Arquivo
@@ -29,7 +29,8 @@ public:
PhotoModifications &modifications, PhotoModifications &modifications,
const QSize &imageSize, const QSize &imageSize,
std::shared_ptr<Controllers> controllers, std::shared_ptr<Controllers> controllers,
Fn<QImage(QRect)> blurSource); Fn<QImage(QRect)> blurSource,
bool fixedCrop = false);
~Paint() override; ~Paint() override;
[[nodiscard]] std::shared_ptr<Scene> saveScene() const; [[nodiscard]] std::shared_ptr<Scene> saveScene() const;
@@ -41,10 +42,24 @@ public:
void keepResult(); void keepResult();
void updateUndoState(); void updateUndoState();
void createTextItem();
void clearSelection();
void setTextColor(const QColor &color);
void setSelectedTextColor(const QColor &color);
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
[[nodiscard]] rpl::producer<QColor> textItemSelections() const;
[[nodiscard]] rpl::producer<> textItemDeselections() const;
[[nodiscard]] rpl::producer<bool> textEditStates() const;
void handleMimeData(const QMimeData *data); void handleMimeData(const QMimeData *data);
void paintImage(QPainter &p, const QPixmap &image) const; void paintImage(QPainter &p, const QPixmap &image) const;
void resetView(); void resetView();
bool zoomSceneItems(float64 wheelDelta, bool fine = false);
void panSceneItems(QPointF sceneDelta);
[[nodiscard]] QPointF mapWidgetDeltaToScene(QPoint delta) const;
private: private:
bool eventFilter(QObject *obj, QEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override;
void updateViewGeometry(); void updateViewGeometry();
@@ -64,6 +79,7 @@ private:
const base::unique_qptr<QGraphicsView> _view; const base::unique_qptr<QGraphicsView> _view;
QPointer<QWidget> _viewport; QPointer<QWidget> _viewport;
const QSize _imageSize; const QSize _imageSize;
const bool _fixedCrop = false;
QRect _imageGeometry; QRect _imageGeometry;
QRect _outerGeometry; QRect _outerGeometry;
@@ -84,6 +100,7 @@ private:
rpl::variable<bool> _hasUndo = true; rpl::variable<bool> _hasUndo = true;
rpl::variable<bool> _hasRedo = true; rpl::variable<bool> _hasRedo = true;
rpl::variable<bool> _textEditing = false;
}; };
+60 -8
Ver Arquivo
@@ -151,7 +151,7 @@ struct BrushState {
const auto tool = ToolFromSerialized(entryTool); const auto tool = ToolFromSerialized(entryTool);
const auto index = ToolIndex(tool); const auto index = ToolIndex(tool);
if (version == kBrushesVersion && size > 0) { if (version == kBrushesVersion && size > 0) {
result.brushes[index].sizeRatio = size / float(kPrecision); result.brushes[index].sizeRatio = size / float64(kPrecision);
} }
if (color.isValid()) { if (color.isValid()) {
result.brushes[index].color = color; result.brushes[index].color = color;
@@ -237,6 +237,7 @@ PhotoEditor::PhotoEditor(
std::move(show), std::move(show),
_brushes, _brushes,
_brushTool)) { _brushTool)) {
_modifications.cropType = data.cropType;
sizeValue( sizeValue(
) | rpl::on_next([=](const QSize &size) { ) | rpl::on_next([=](const QSize &size) {
@@ -302,6 +303,11 @@ PhotoEditor::PhotoEditor(
}; };
}, lifetime()); }, lifetime());
_controls->textRequests(
) | rpl::on_next([=] {
_content->createTextItem();
}, lifetime());
_controls->doneRequests( _controls->doneRequests(
) | rpl::on_next([=] { ) | rpl::on_next([=] {
const auto mode = _mode.current().mode; const auto mode = _mode.current().mode;
@@ -336,18 +342,64 @@ PhotoEditor::PhotoEditor(
} }
}, lifetime()); }, lifetime());
_colorPicker->toolClicks(
) | rpl::on_next([=] {
_content->clearSelection();
}, lifetime());
_colorPicker->saveBrushRequests( _colorPicker->saveBrushRequests(
) | rpl::on_next([=](const Brush &brush) { ) | rpl::on_next([=](const Brush &brush) {
_content->applyBrush(brush); if (_textItemSelected || _textEditing) {
_content->setSelectedTextColor(brush.color);
_content->setTextColor(brush.color);
} else {
_content->applyBrush(brush);
_content->setTextColor(brush.color);
_brushTool = brush.tool; _brushTool = brush.tool;
_brushes[ToolIndex(brush.tool)] = brush; _brushes[ToolIndex(brush.tool)] = brush;
const auto serialized = Serialize(_brushes, _brushTool); const auto serialized = Serialize(_brushes, _brushTool);
if (Core::App().settings().photoEditorBrush() != serialized) { if (Core::App().settings().photoEditorBrush() != serialized) {
Core::App().settings().setPhotoEditorBrush(serialized); Core::App().settings().setPhotoEditorBrush(serialized);
Core::App().saveSettingsDelayed(); Core::App().saveSettingsDelayed();
}
} }
}, lifetime()); }, lifetime());
_content->textEditStates(
) | rpl::on_next([=](bool editing) {
_textEditing = editing;
if (_textEditing) {
_colorPicker->setToolSelectionVisible(false);
} else if (!_textItemSelected) {
const auto &brush = _brushes[ToolIndex(_brushTool)];
_colorPicker->setColor(brush.color);
_colorPicker->setToolSelectionVisible(true);
}
}, lifetime());
_content->textColorRequests(
) | rpl::on_next([=](const QColor &color) {
_colorPicker->setColor(color);
}, lifetime());
_content->textItemSelections(
) | rpl::on_next([=](const QColor &color) {
_textItemSelected = true;
_colorPicker->setToolSelectionVisible(false);
_colorPicker->setColor(color);
}, lifetime());
_content->textItemDeselections(
) | rpl::on_next([=] {
_textItemSelected = false;
if (_textEditing) {
return;
}
const auto &brush = _brushes[ToolIndex(_brushTool)];
_colorPicker->setColor(brush.color);
_colorPicker->setToolSelectionVisible(true);
}, lifetime());
} }
void PhotoEditor::keyPressEvent(QKeyEvent *e) { void PhotoEditor::keyPressEvent(QKeyEvent *e) {
+2
Ver Arquivo
@@ -72,6 +72,8 @@ private:
.mode = PhotoEditorMode::Mode::Transform, .mode = PhotoEditorMode::Mode::Transform,
.action = PhotoEditorMode::Action::None, .action = PhotoEditorMode::Action::None,
}; };
bool _textItemSelected = false;
bool _textEditing = false;
rpl::event_stream<PhotoModifications> _done; rpl::event_stream<PhotoModifications> _done;
rpl::event_stream<> _cancel; rpl::event_stream<> _cancel;
@@ -9,8 +9,40 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene.h" #include "editor/scene/scene.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/userpic_view.h"
namespace Editor { namespace Editor {
namespace {
void ApplyShapeMask(QImage &image, EditorData::CropType type) {
if (type == EditorData::CropType::Rect) {
return;
}
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
auto mask = QImage(image.size(), QImage::Format_ARGB32_Premultiplied);
mask.fill(Qt::transparent);
{
auto p = QPainter(&mask);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
const auto rect = QRectF(QPointF(), QSizeF(image.size()));
if (type == EditorData::CropType::Ellipse) {
p.drawEllipse(rect);
} else {
const auto radius = std::min(rect.width(), rect.height())
* Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(rect, radius, radius);
}
}
auto p = QPainter(&image);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(0, 0, mask);
}
} // namespace
QImage ImageModified(QImage image, const PhotoModifications &mods) { QImage ImageModified(QImage image, const PhotoModifications &mods) {
Expects(!image.isNull()); Expects(!image.isNull());
@@ -32,6 +64,7 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
auto cropped = mods.crop.isValid() auto cropped = mods.crop.isValid()
? image.copy(mods.crop) ? image.copy(mods.crop)
: image; : image;
ApplyShapeMask(cropped, mods.cropType);
QTransform transform; QTransform transform;
if (mods.flipped) { if (mods.flipped) {
transform.scale(-1, 1); transform.scale(-1, 1);
@@ -43,7 +76,11 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
} }
bool PhotoModifications::empty() const { bool PhotoModifications::empty() const {
return !angle && !flipped && !crop.isValid() && !paint; return !angle
&& !flipped
&& !crop.isValid()
&& cropType == EditorData::CropType::Rect
&& !paint;
} }
PhotoModifications::operator bool() const { PhotoModifications::operator bool() const {
+14 -12
Ver Arquivo
@@ -11,18 +11,6 @@ namespace Editor {
class Scene; class Scene;
struct PhotoModifications {
int angle = 0;
bool flipped = false;
QRect crop;
std::shared_ptr<Scene> paint = nullptr;
[[nodiscard]] bool empty() const;
[[nodiscard]] explicit operator bool() const;
~PhotoModifications();
};
struct EditorData { struct EditorData {
enum class CropType { enum class CropType {
Rect, Rect,
@@ -35,6 +23,20 @@ struct EditorData {
QSize exactSize; QSize exactSize;
CropType cropType = CropType::Rect; CropType cropType = CropType::Rect;
bool keepAspectRatio = false; bool keepAspectRatio = false;
bool fixedCrop = false;
};
struct PhotoModifications {
int angle = 0;
bool flipped = false;
QRect crop;
EditorData::CropType cropType = EditorData::CropType::Rect;
std::shared_ptr<Scene> paint = nullptr;
[[nodiscard]] bool empty() const;
[[nodiscard]] explicit operator bool() const;
~PhotoModifications();
}; };
[[nodiscard]] QImage ImageModified( [[nodiscard]] QImage ImageModified(
@@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_pip.h" #include "media/view/media_view_pip.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
#include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent>
namespace Editor { namespace Editor {
using Media::View::FlipSizeByRotation; using Media::View::FlipSizeByRotation;
@@ -26,6 +29,7 @@ PhotoEditorContent::PhotoEditorContent(
EditorData data) EditorData data)
: RpWidget(parent) : RpWidget(parent)
, _photoSize(photo->size()) , _photoSize(photo->size())
, _fixedCrop(data.fixedCrop)
, _paint(base::make_unique_q<Paint>( , _paint(base::make_unique_q<Paint>(
this, this,
modifications, modifications,
@@ -42,7 +46,8 @@ PhotoEditorContent::PhotoEditorContent(
auto result = img.copy(pixelRect.intersected(img.rect())); auto result = img.copy(pixelRect.intersected(img.rect()));
result.setDevicePixelRatio(dpr); result.setDevicePixelRatio(dpr);
return result; return result;
})) },
data.fixedCrop))
, _crop(base::make_unique_q<Crop>( , _crop(base::make_unique_q<Crop>(
this, this,
modifications, modifications,
@@ -110,6 +115,48 @@ PhotoEditorContent::PhotoEditorContent(
}, lifetime()); }, lifetime());
setupDragArea(); setupDragArea();
if (_fixedCrop) {
const auto pan = _crop->lifetime().make_state<
std::optional<QPoint>
>();
_crop->events(
) | rpl::on_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Wheel) {
const auto wheel = static_cast<QWheelEvent*>(e.get());
const auto raw = wheel->angleDelta();
_paint->zoomSceneItems(
raw.y() ? raw.y() : raw.x(),
wheel->modifiers().testFlag(Qt::ShiftModifier));
e->accept();
} else if (type == QEvent::MouseButtonPress) {
const auto mouse = static_cast<QMouseEvent*>(e.get());
if (mouse->button() == Qt::MiddleButton) {
*pan = mouse->pos();
_crop->setCursor(Qt::ClosedHandCursor);
e->accept();
}
} else if (type == QEvent::MouseMove) {
if (pan->has_value()) {
const auto mouse = static_cast<QMouseEvent*>(e.get());
const auto point = mouse->pos();
const auto delta = point - **pan;
*pan = point;
_paint->panSceneItems(
_paint->mapWidgetDeltaToScene(delta));
e->accept();
}
} else if (type == QEvent::MouseButtonRelease) {
const auto mouse = static_cast<QMouseEvent*>(e.get());
if (mouse->button() == Qt::MiddleButton && pan->has_value()) {
pan->reset();
_crop->unsetCursor();
e->accept();
}
}
}, _crop->lifetime());
}
} }
void PhotoEditorContent::applyModifications( void PhotoEditorContent::applyModifications(
@@ -162,6 +209,38 @@ void PhotoEditorContent::applyBrush(const Brush &brush) {
_paint->applyBrush(brush); _paint->applyBrush(brush);
} }
void PhotoEditorContent::createTextItem() {
_paint->createTextItem();
}
void PhotoEditorContent::clearSelection() {
_paint->clearSelection();
}
void PhotoEditorContent::setTextColor(const QColor &color) {
_paint->setTextColor(color);
}
void PhotoEditorContent::setSelectedTextColor(const QColor &color) {
_paint->setSelectedTextColor(color);
}
rpl::producer<QColor> PhotoEditorContent::textColorRequests() const {
return _paint->textColorRequests();
}
rpl::producer<QColor> PhotoEditorContent::textItemSelections() const {
return _paint->textItemSelections();
}
rpl::producer<> PhotoEditorContent::textItemDeselections() const {
return _paint->textItemDeselections();
}
rpl::producer<bool> PhotoEditorContent::textEditStates() const {
return _paint->textEditStates();
}
bool PhotoEditorContent::handleKeyPress(not_null<QKeyEvent*> e) const { bool PhotoEditorContent::handleKeyPress(not_null<QKeyEvent*> e) const {
return false; return false;
} }
@@ -31,6 +31,15 @@ public:
void applyModifications(PhotoModifications modifications); void applyModifications(PhotoModifications modifications);
void applyMode(const PhotoEditorMode &mode); void applyMode(const PhotoEditorMode &mode);
void applyBrush(const Brush &brush); void applyBrush(const Brush &brush);
void createTextItem();
void clearSelection();
void setTextColor(const QColor &color);
void setSelectedTextColor(const QColor &color);
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
[[nodiscard]] rpl::producer<QColor> textItemSelections() const;
[[nodiscard]] rpl::producer<> textItemDeselections() const;
[[nodiscard]] rpl::producer<bool> textEditStates() const;
void applyAspectRatio(float64 ratio); void applyAspectRatio(float64 ratio);
void save(PhotoModifications &modifications); void save(PhotoModifications &modifications);
@@ -45,6 +54,7 @@ public:
private: private:
const QSize _photoSize; const QSize _photoSize;
const bool _fixedCrop = false;
const base::unique_qptr<Paint> _paint; const base::unique_qptr<Paint> _paint;
const base::unique_qptr<Crop> _crop; const base::unique_qptr<Crop> _crop;
const std::shared_ptr<Image> _photo; const std::shared_ptr<Image> _photo;
@@ -202,6 +202,38 @@ ButtonBar::ButtonBar(
}, lifetime()); }, lifetime());
} }
class TextToolButton final : public Ui::AbstractButton {
public:
TextToolButton(not_null<QWidget*> parent)
: AbstractButton(parent) {
constexpr auto kSizeShrink = 6;
resize(
st::photoEditorStickersButton.width - kSizeShrink,
st::photoEditorStickersButton.height - kSizeShrink);
events(
) | rpl::on_next([=](not_null<QEvent*> event) {
if (event->type() == QEvent::Enter
|| event->type() == QEvent::Leave) {
update();
}
}, lifetime());
}
private:
void paintEvent(QPaintEvent *) override {
auto p = QPainter(this);
auto hq = PainterHighQualityEnabler(p);
auto font = st::semiboldFont->f;
font.setPixelSize(QWidget::rect().height() / 2);
p.setFont(font);
p.setPen(isOver()
? st::photoEditorButtonIconFgOver
: st::photoEditorButtonIconFg);
p.translate(0, st::lineWidth * 3);
p.drawText(QWidget::rect(), style::al_center, u"A"_q);
}
};
PhotoEditorControls::PhotoEditorControls( PhotoEditorControls::PhotoEditorControls(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
std::shared_ptr<Controllers> controllers, std::shared_ptr<Controllers> controllers,
@@ -272,6 +304,7 @@ PhotoEditorControls::PhotoEditorControls(
_paintBottomButtons, _paintBottomButtons,
st::photoEditorStickersButton) st::photoEditorStickersButton)
: nullptr) : nullptr)
, _textButton(base::make_unique_q<TextToolButton>(_paintBottomButtons))
, _paintDone(base::make_unique_q<EdgeButton>( , _paintDone(base::make_unique_q<EdgeButton>(
_paintBottomButtons, _paintBottomButtons,
tr::lng_box_done(tr::now), tr::lng_box_done(tr::now),
@@ -499,6 +532,10 @@ rpl::producer<> PhotoEditorControls::paintModeRequests() const {
return _paintModeButton->clicks() | rpl::to_empty; return _paintModeButton->clicks() | rpl::to_empty;
} }
rpl::producer<> PhotoEditorControls::textRequests() const {
return _textButton->clicks() | rpl::to_empty;
}
rpl::producer<> PhotoEditorControls::doneRequests() const { rpl::producer<> PhotoEditorControls::doneRequests() const {
return rpl::merge( return rpl::merge(
_transformDone->clicks() | rpl::to_empty, _transformDone->clicks() | rpl::to_empty,
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/photo_editor_inner_common.h" #include "editor/photo_editor_inner_common.h"
namespace Ui { namespace Ui {
class AbstractButton;
class IconButton; class IconButton;
class FlatLabel; class FlatLabel;
class PopupMenu; class PopupMenu;
@@ -41,6 +42,7 @@ public:
[[nodiscard]] rpl::producer<int> rotateRequests() const; [[nodiscard]] rpl::producer<int> rotateRequests() const;
[[nodiscard]] rpl::producer<> flipRequests() const; [[nodiscard]] rpl::producer<> flipRequests() const;
[[nodiscard]] rpl::producer<> paintModeRequests() const; [[nodiscard]] rpl::producer<> paintModeRequests() const;
[[nodiscard]] rpl::producer<> textRequests() const;
[[nodiscard]] rpl::producer<> doneRequests() const; [[nodiscard]] rpl::producer<> doneRequests() const;
[[nodiscard]] rpl::producer<> cancelRequests() const; [[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer<QPoint> colorLinePositionValue() const; [[nodiscard]] rpl::producer<QPoint> colorLinePositionValue() const;
@@ -82,6 +84,7 @@ private:
const base::unique_qptr<Ui::IconButton> _redoButton; const base::unique_qptr<Ui::IconButton> _redoButton;
const base::unique_qptr<Ui::IconButton> _paintModeButtonActive; const base::unique_qptr<Ui::IconButton> _paintModeButtonActive;
const base::unique_qptr<Ui::IconButton> _stickersButton; const base::unique_qptr<Ui::IconButton> _stickersButton;
const base::unique_qptr<Ui::AbstractButton> _textButton;
const base::unique_qptr<EdgeButton> _paintDone; const base::unique_qptr<EdgeButton> _paintDone;
base::unique_qptr<Ui::PopupMenu> _ratioMenu; base::unique_qptr<Ui::PopupMenu> _ratioMenu;
+410 -5
Ver Arquivo
@@ -10,11 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene_item_canvas.h" #include "editor/scene/scene_item_canvas.h"
#include "editor/scene/scene_item_line.h" #include "editor/scene/scene_item_line.h"
#include "editor/scene/scene_item_sticker.h" #include "editor/scene/scene_item_sticker.h"
#include "editor/scene/scene_item_text.h"
#include "editor/scene/scene_emoji_document.h"
#include "ui/image/image_prepare.h" #include "ui/image/image_prepare.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "styles/style_editor.h" #include "styles/style_editor.h"
#include <QGraphicsSceneContextMenuEvent>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QGraphicsTextItem>
#include <QGraphicsView>
#include <QTextCursor>
#include <QTextDocument>
namespace Editor { namespace Editor {
namespace { namespace {
@@ -100,6 +107,50 @@ bool SkipMouseEvent(not_null<QGraphicsSceneMouseEvent*> event) {
return event->isAccepted() || (event->button() == Qt::RightButton); return event->isAccepted() || (event->button() == Qt::RightButton);
} }
constexpr auto kPaddingFactor = 0.4;
constexpr auto kMaxWidthFactor = 0.8;
constexpr auto kMinWidthFactor = 0.16;
constexpr auto kIdealWidthExtra = 2;
constexpr auto kDefaultFontSizeDivisor = 15.;
constexpr auto kScaleThreshold = 0.01;
class TextEditProxy final : public QGraphicsTextItem {
public:
using QGraphicsTextItem::QGraphicsTextItem;
Fn<void()> onFinish;
Fn<void()> onCancel;
protected:
void keyPressEvent(QKeyEvent *event) override {
if (event->key() == Qt::Key_Escape) {
fire(onCancel);
return;
}
QGraphicsTextItem::keyPressEvent(event);
}
void focusOutEvent(QFocusEvent *event) override {
QGraphicsTextItem::focusOutEvent(event);
fire(onFinish);
}
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override {
event->accept();
}
private:
void fire(Fn<void()> &callback) {
if (!callback) {
return;
}
const auto cb = std::exchange(callback, nullptr);
onFinish = nullptr;
onCancel = nullptr;
crl::on_main(cb);
}
};
} // namespace } // namespace
Scene::Scene(const QRectF &rect) Scene::Scene(const QRectF &rect)
@@ -237,9 +288,34 @@ Scene::Scene(const QRectF &rect)
addItem(item); addItem(item);
_canvas->setZValue(++_lastLineZ); _canvas->setZValue(++_lastLineZ);
}, _lifetime); }, _lifetime);
QObject::connect(
this,
&QGraphicsScene::selectionChanged,
[=] {
const auto selected = selectedItems();
auto *textItem = (ItemText*)(nullptr);
if (selected.size() == 1
&& selected.front()->type() == ItemText::Type) {
textItem = static_cast<ItemText*>(selected.front());
}
const auto changed = (textItem != _selectedTextItem);
if (!changed) {
return;
}
_selectedTextItem = textItem;
if (textItem) {
_textItemSelections.fire_copy(textItem->color());
} else {
_textItemDeselections.fire({});
}
});
} }
void Scene::cancelDrawing() { void Scene::cancelDrawing() {
if (_textEdit.proxy) {
finishTextEditing(false);
}
_canvas->cancelDrawing(); _canvas->cancelDrawing();
} }
@@ -271,6 +347,16 @@ void Scene::removeItem(const ItemPtr &item) {
} }
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) { void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (_textEdit.proxy) {
const auto clickOnProxy = _textEdit.proxy->contains(
_textEdit.proxy->mapFromScene(event->scenePos()));
if (!clickOnProxy) {
finishTextEditing(true);
QGraphicsScene::mousePressEvent(event);
return;
}
}
QGraphicsScene::mousePressEvent(event); QGraphicsScene::mousePressEvent(event);
if (SkipMouseEvent(event) || !sceneRect().contains(event->scenePos())) { if (SkipMouseEvent(event) || !sceneRect().contains(event->scenePos())) {
return; return;
@@ -280,7 +366,7 @@ void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseReleaseEvent(event); QGraphicsScene::mouseReleaseEvent(event);
if (SkipMouseEvent(event)) { if (SkipMouseEvent(event) || _textEdit.proxy) {
return; return;
} }
_canvas->handleMouseReleaseEvent(event); _canvas->handleMouseReleaseEvent(event);
@@ -288,16 +374,58 @@ void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseMoveEvent(event); QGraphicsScene::mouseMoveEvent(event);
if (SkipMouseEvent(event)) { if (SkipMouseEvent(event) || _textEdit.proxy) {
return; return;
} }
_canvas->handleMouseMoveEvent(event); _canvas->handleMouseMoveEvent(event);
} }
void Scene::applyBrush(const QColor &color, float size, Brush::Tool tool) { void Scene::applyBrush(const QColor &color, float64 size, Brush::Tool tool) {
_canvas->applyBrush(color, size, tool); _canvas->applyBrush(color, size, tool);
} }
void Scene::setTextDefaults(
const QColor &color,
float64 fontSize,
int style) {
_textColor = color;
_textFontSize = fontSize;
_textStyle = style;
}
void Scene::setTextColor(const QColor &color) {
_textColor = color;
if (_textEdit.proxy) {
_textEdit.proxy->setDefaultTextColor(EffectiveTextColor(
color,
static_cast<TextStyle>(_textEditStyle)));
}
}
void Scene::setSelectedTextColor(const QColor &color) {
for (auto *item : selectedItems()) {
if (item->type() == ItemText::Type) {
static_cast<ItemText*>(item)->setColor(color);
}
}
}
rpl::producer<QColor> Scene::textColorRequests() const {
return _textColorRequests.events();
}
rpl::producer<QColor> Scene::textItemSelections() const {
return _textItemSelections.events();
}
rpl::producer<> Scene::textItemDeselections() const {
return _textItemDeselections.events();
}
rpl::producer<bool> Scene::textEditStates() const {
return _textEditStates.events();
}
void Scene::setBlurSource(Fn<QImage(QRect)> source) { void Scene::setBlurSource(Fn<QImage(QRect)> source) {
_blurSource = std::move(source); _blurSource = std::move(source);
} }
@@ -328,6 +456,7 @@ std::shared_ptr<float64> Scene::lastZ() const {
} }
void Scene::updateZoom(float64 zoom) { void Scene::updateZoom(float64 zoom) {
_currentZoom = zoom;
_canvas->updateZoom(zoom); _canvas->updateZoom(zoom);
for (const auto &item : items()) { for (const auto &item : items()) {
if (item->type() >= ItemBase::Type) { if (item->type() >= ItemBase::Type) {
@@ -396,6 +525,10 @@ void Scene::clearRedoList() {
} }
void Scene::save(SaveState state) { void Scene::save(SaveState state) {
if (_textEdit.proxy) {
finishTextEditing(true);
}
removeIf([](const ItemPtr &item) { removeIf([](const ItemPtr &item) {
return item->isRemovedStatus() return item->isRemovedStatus()
&& !item->hasState(SaveState::Keep) && !item->hasState(SaveState::Keep)
@@ -421,11 +554,283 @@ void Scene::restore(SaveState state) {
cancelDrawing(); cancelDrawing();
} }
void Scene::setTextEditing(bool editing) {
if (_textEditing == editing) {
return;
}
_textEditing = editing;
_textEditStates.fire_copy(editing);
}
void Scene::setupTextProxy(
QGraphicsTextItem *proxy,
const QColor &color,
float64 fontSize) {
proxy->setTextInteractionFlags(Qt::TextEditorInteraction);
proxy->setDefaultTextColor(color);
auto *emojiDoc = new EmojiDocument(proxy);
emojiDoc->setDocumentMargin(0);
proxy->setDocument(emojiDoc);
auto font = QFont();
font.setPixelSize(int(fontSize));
font.setWeight(QFont::DemiBold);
proxy->setFont(font);
{
auto option = emojiDoc->defaultTextOption();
option.setAlignment(Qt::AlignCenter);
emojiDoc->setDefaultTextOption(option);
}
}
void Scene::createTextAtCenter() {
if (_textEdit.proxy) {
return;
}
const auto generation = ++_textEditGeneration;
clearSelection();
cancelDrawing();
setTextEditing(true);
_textEditStyle = _textStyle;
_textEdit.proxy.reset(new TextEditProxy());
const auto proxy = _textEdit.proxy.get();
setupTextProxy(
proxy,
EffectiveTextColor(
_textColor,
static_cast<TextStyle>(_textEditStyle)),
_textFontSize);
const auto emojiDoc = proxy->document();
const auto shortSide = std::min(
sceneRect().width(),
sceneRect().height());
const auto padding = int(_textFontSize * kPaddingFactor);
const auto maxTextWidth = std::max(
int(shortSide * kMaxWidthFactor) - 2 * padding,
1);
const auto minTextWidth = std::clamp(
int(shortSide * kMinWidthFactor) - 2 * padding,
1,
maxTextWidth);
const auto sceneCenter = sceneRect().center();
const auto adjustWidth = [=] {
emojiDoc->setTextWidth(maxTextWidth);
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
const auto width = std::clamp(
ideal + kIdealWidthExtra,
minTextWidth,
maxTextWidth);
proxy->setTextWidth(width);
proxy->setPos(sceneCenter.x() - width / 2., sceneCenter.y());
};
adjustWidth();
QObject::connect(emojiDoc, &QTextDocument::contentsChanged, [=] {
ReplaceEmoji(emojiDoc);
adjustWidth();
});
QGraphicsScene::addItem(proxy);
proxy->setZValue((*_lastZ)++);
proxy->setFocus();
if (!views().isEmpty()) {
views().first()->setFocus();
}
const auto raw = static_cast<TextEditProxy*>(proxy);
raw->onFinish = crl::guard(this, [=] {
if (generation == _textEditGeneration) {
finishTextEditing(true);
}
});
raw->onCancel = crl::guard(this, [=] {
if (generation == _textEditGeneration) {
finishTextEditing(false);
}
});
_textEdit.item.reset();
_textColorRequests.fire_copy(_textColor);
}
void Scene::startTextEditing(ItemText *item) {
if (_textEdit.proxy) {
finishTextEditing(true);
}
if (!item) {
return;
}
const auto generation = ++_textEditGeneration;
cancelDrawing();
setTextEditing(true);
_textEditStyle = int(item->textStyle());
_textEdit.proxy.reset(new TextEditProxy());
const auto proxy = _textEdit.proxy.get();
setupTextProxy(
proxy,
EffectiveTextColor(item->color(), item->textStyle()),
item->fontSize());
proxy->setPlainText(item->text());
ReplaceEmoji(proxy->document());
const auto emojiDoc = proxy->document();
const auto shortSide = std::min(
sceneRect().width(),
sceneRect().height());
const auto padding = int(item->fontSize() * kPaddingFactor);
const auto maxTextWidth = std::max(
int(shortSide * kMaxWidthFactor) - 2 * padding,
1);
const auto minTextWidth = std::clamp(
int(shortSide * kMinWidthFactor) - 2 * padding,
1,
maxTextWidth);
const auto anchor = item->scenePos();
const auto adjustWidth = [=] {
emojiDoc->setTextWidth(maxTextWidth);
const auto ideal = int(std::ceil(emojiDoc->idealWidth()));
const auto width = std::clamp(
ideal + kIdealWidthExtra,
minTextWidth,
maxTextWidth);
proxy->setTextWidth(width);
const auto center = proxy->boundingRect().center();
proxy->setTransformOriginPoint(center);
proxy->setPos(anchor - center);
};
adjustWidth();
QObject::connect(emojiDoc, &QTextDocument::contentsChanged, [=] {
ReplaceEmoji(emojiDoc);
adjustWidth();
});
const auto scale = item->editScale();
proxy->setRotation(item->rotation());
if (std::abs(scale - 1.) > kScaleThreshold) {
proxy->setScale(scale);
}
QGraphicsScene::addItem(proxy);
proxy->setZValue((*_lastZ)++);
proxy->setFocus();
auto cursor = proxy->textCursor();
cursor.select(QTextCursor::Document);
proxy->setTextCursor(cursor);
item->setVisible(false);
const auto raw = static_cast<TextEditProxy*>(proxy);
raw->onFinish = crl::guard(this, [=] {
if (generation == _textEditGeneration) {
finishTextEditing(true);
}
});
raw->onCancel = crl::guard(this, [=] {
if (generation == _textEditGeneration) {
finishTextEditing(false);
}
});
const auto it = _itemsByPointer.find(item);
_textEdit.item = (it != end(_itemsByPointer))
? it->second
: std::weak_ptr<NumberedItem>();
_textColorRequests.fire_copy(item->color());
}
void Scene::finishTextEditing(bool save) {
if (!_textEdit.proxy) {
return;
}
const auto text = save
? RecoverTextFromDocument(_textEdit.proxy->document()).trimmed()
: QString();
const auto proxyRect = _textEdit.proxy->boundingRect();
const auto proxyCenter = _textEdit.proxy->pos()
+ QPointF(proxyRect.width() / 2., proxyRect.height() / 2.);
const auto lockedItem = _textEdit.item.lock();
auto *existingItem = lockedItem
? static_cast<ItemText*>(lockedItem.get())
: (ItemText*)(nullptr);
const auto raw = static_cast<TextEditProxy*>(_textEdit.proxy.get());
raw->onFinish = nullptr;
raw->onCancel = nullptr;
QGraphicsScene::removeItem(_textEdit.proxy.get());
_textEdit.proxy = nullptr;
_textEdit.item.reset();
setTextEditing(false);
const auto defaultStyle = static_cast<TextStyle>(_textStyle);
if (!text.isEmpty()) {
if (existingItem) {
existingItem->setText(text);
existingItem->setVisible(true);
} else {
const auto imageSize = sceneRect().size().toSize();
const auto contentSize = ItemText::computeContentSize(
text,
_textFontSize,
imageSize,
defaultStyle);
const auto zoom = (_currentZoom > 0.) ? _currentZoom : 1.;
const auto handleInflate = int(
std::ceil(st::photoEditorItemHandleSize / zoom));
const auto size = std::max(
contentSize.width() + handleInflate,
1);
auto data = ItemBase::Data{
.initialZoom = zoom,
.zPtr = _lastZ,
.size = size,
.x = int(proxyCenter.x()),
.y = int(proxyCenter.y()),
.imageSize = imageSize,
};
auto item = std::make_shared<ItemText>(
text,
_textColor,
_textFontSize,
defaultStyle,
imageSize,
std::move(data));
addItem(item);
}
} else if (existingItem) {
if (save) {
removeItem(existingItem);
} else {
existingItem->setVisible(true);
}
}
}
Scene::~Scene() { Scene::~Scene() {
// Prevent destroying by scene of all items. if (_textEdit.proxy) {
setTextEditing(false);
const auto raw = static_cast<TextEditProxy*>(
_textEdit.proxy.get());
raw->onFinish = nullptr;
raw->onCancel = nullptr;
QGraphicsScene::removeItem(_textEdit.proxy.get());
_textEdit.proxy = nullptr;
}
QGraphicsScene::removeItem(_canvas.get()); QGraphicsScene::removeItem(_canvas.get());
for (const auto &item : items()) { for (const auto &item : items()) {
// Scene loses ownership of an item.
QGraphicsScene::removeItem(item.get()); QGraphicsScene::removeItem(item.get());
} }
} }
+39 -1
Ver Arquivo
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QGraphicsScene> #include <QGraphicsScene>
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
class QGraphicsTextItem;
namespace Ui { namespace Ui {
class RpWidget; class RpWidget;
@@ -21,6 +22,7 @@ class RpWidget;
namespace Editor { namespace Editor {
class ItemCanvas; class ItemCanvas;
class ItemText;
class NumberedItem; class NumberedItem;
class Scene final : public QGraphicsScene { class Scene final : public QGraphicsScene {
@@ -29,8 +31,9 @@ public:
Scene(const QRectF &rect); Scene(const QRectF &rect);
~Scene(); ~Scene();
void applyBrush(const QColor &color, float size, Brush::Tool tool); void applyBrush(const QColor &color, float64 size, Brush::Tool tool);
void setBlurSource(Fn<QImage(QRect)> source); void setBlurSource(Fn<QImage(QRect)> source);
void setTextDefaults(const QColor &color, float64 fontSize, int style);
[[nodiscard]] std::vector<ItemPtr> items( [[nodiscard]] std::vector<ItemPtr> items(
Qt::SortOrder order = Qt::DescendingOrder) const; Qt::SortOrder order = Qt::DescendingOrder) const;
@@ -46,6 +49,16 @@ public:
void cancelDrawing(); void cancelDrawing();
void startTextEditing(ItemText *item);
void createTextAtCenter();
void setTextColor(const QColor &color);
void setSelectedTextColor(const QColor &color);
[[nodiscard]] rpl::producer<QColor> textColorRequests() const;
[[nodiscard]] rpl::producer<QColor> textItemSelections() const;
[[nodiscard]] rpl::producer<> textItemDeselections() const;
[[nodiscard]] rpl::producer<bool> textEditStates() const;
[[nodiscard]] bool hasUndo() const; [[nodiscard]] bool hasUndo() const;
[[nodiscard]] bool hasRedo() const; [[nodiscard]] bool hasRedo() const;
@@ -62,6 +75,13 @@ protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private: private:
void removeIf(Fn<bool(const ItemPtr &)> proj); void removeIf(Fn<bool(const ItemPtr &)> proj);
void finishTextEditing(bool save);
void setTextEditing(bool editing);
void setupTextProxy(
QGraphicsTextItem *proxy,
const QColor &color,
float64 fontSize);
const std::shared_ptr<ItemCanvas> _canvas; const std::shared_ptr<ItemCanvas> _canvas;
const std::shared_ptr<float64> _lastZ; const std::shared_ptr<float64> _lastZ;
Fn<QImage(QRect)> _blurSource; Fn<QImage(QRect)> _blurSource;
@@ -70,9 +90,27 @@ private:
std::unordered_map<QGraphicsItem*, ItemPtr> _itemsByPointer; std::unordered_map<QGraphicsItem*, ItemPtr> _itemsByPointer;
float64 _lastLineZ = 0.; float64 _lastLineZ = 0.;
float64 _currentZoom = 1.;
int _itemNumber = 0; int _itemNumber = 0;
QColor _textColor;
float64 _textFontSize = 0.;
int _textStyle = 0;
int _textEditStyle = 0;
struct {
std::weak_ptr<NumberedItem> item;
base::unique_qptr<QGraphicsTextItem> proxy;
} _textEdit;
rpl::event_stream<> _addsItem, _removesItem; rpl::event_stream<> _addsItem, _removesItem;
rpl::event_stream<QColor> _textColorRequests;
rpl::event_stream<QColor> _textItemSelections;
rpl::event_stream<> _textItemDeselections;
rpl::event_stream<bool> _textEditStates;
ItemText *_selectedTextItem = nullptr;
bool _textEditing = false;
int _textEditGeneration = 0;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };
@@ -0,0 +1,132 @@
/*
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 "editor/scene/scene_emoji_document.h"
#include "ui/emoji_config.h"
#include "ui/painter.h"
#include <QTextBlock>
#include <QTextCursor>
namespace Editor {
EmojiDocument::EmojiDocument(QObject *parent)
: QTextDocument(parent) {
}
QVariant EmojiDocument::loadResource(int type, const QUrl &name) {
if (type != QTextDocument::ImageResource
|| name.scheme() != u"emoji"_q) {
return QTextDocument::loadResource(type, name);
}
const auto i = _cache.find(name);
if (i != _cache.end()) {
return i->second;
}
auto result = QVariant();
if (const auto emoji = Ui::Emoji::FromUrl(name.toDisplayString())) {
const auto factor = style::DevicePixelRatio();
const auto logical = QFontMetrics(defaultFont()).height();
const auto source = Ui::Emoji::GetSizeLarge();
auto image = QImage(
QSize(logical, logical) * factor,
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(factor);
image.fill(Qt::transparent);
{
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
const auto sourceLogical = source / float64(factor);
const auto scale = logical / sourceLogical;
p.scale(scale, scale);
Ui::Emoji::Draw(p, emoji, source, 0, 0);
}
result = QVariant(QPixmap::fromImage(std::move(image)));
}
_cache.emplace(name, result);
return result;
}
void ReplaceEmoji(QTextDocument *doc) {
QSignalBlocker blocker(doc);
const auto fontHeight = QFontMetrics(doc->defaultFont()).height();
auto cursor = QTextCursor(doc);
auto block = doc->begin();
while (block.isValid()) {
auto text = block.text();
auto start = text.constData();
auto end = start + text.size();
auto ch = start;
while (ch < end) {
auto emojiLength = 0;
const auto emoji = Ui::Emoji::Find(ch, end, &emojiLength);
if (!emoji || emojiLength <= 0) {
++ch;
continue;
}
const auto pos = block.position() + int(ch - start);
cursor.setPosition(pos);
cursor.setPosition(
pos + emojiLength,
QTextCursor::KeepAnchor);
auto format = QTextImageFormat();
format.setName(emoji->toUrl());
format.setWidth(fontHeight);
format.setHeight(fontHeight);
format.setVerticalAlignment(
QTextCharFormat::AlignBaseline);
cursor.insertImage(format);
block = doc->findBlock(pos);
text = block.text();
start = text.constData();
end = start + text.size();
ch = start + (pos - block.position()) + 1;
continue;
}
block = block.next();
}
}
QString RecoverTextFromDocument(QTextDocument *doc) {
auto result = QString();
auto block = doc->begin();
while (block.isValid()) {
if (block != doc->begin()) {
result += '\n';
}
auto it = block.begin();
while (!it.atEnd()) {
const auto fragment = it.fragment();
if (!fragment.isValid()) {
++it;
continue;
}
const auto text = fragment.text();
const auto format = fragment.charFormat();
for (const auto &ch : text) {
if (ch == QChar::ObjectReplacementCharacter) {
if (format.isImageFormat()) {
const auto name = format.toImageFormat().name();
if (const auto emoji = Ui::Emoji::FromUrl(name)) {
result += emoji->text();
continue;
}
}
}
result += ch;
}
++it;
}
block = block.next();
}
return result;
}
} // namespace Editor
@@ -0,0 +1,26 @@
/*
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 <QTextDocument>
namespace Editor {
class EmojiDocument final : public QTextDocument {
public:
explicit EmojiDocument(QObject *parent = nullptr);
QVariant loadResource(int type, const QUrl &name) override;
private:
std::map<QUrl, QVariant> _cache;
};
void ReplaceEmoji(QTextDocument *doc);
[[nodiscard]] QString RecoverTextFromDocument(QTextDocument *doc);
} // namespace Editor
@@ -329,6 +329,7 @@ void ItemBase::updateVerticalSize() {
} }
void ItemBase::setAspectRatio(float64 aspectRatio) { void ItemBase::setAspectRatio(float64 aspectRatio) {
prepareGeometryChange();
_aspectRatio = aspectRatio; _aspectRatio = aspectRatio;
updateVerticalSize(); updateVerticalSize();
} }
@@ -257,7 +257,7 @@ void ItemCanvas::drawArrowHead() {
} }
direction /= length; direction /= length;
const auto angle = qDegreesToRadians( const auto angle = qDegreesToRadians(
double(st::photoEditorArrowHeadAngleDegrees)); float64(st::photoEditorArrowHeadAngleDegrees));
const auto sinA = std::sin(angle); const auto sinA = std::sin(angle);
const auto cosA = std::cos(angle); const auto cosA = std::cos(angle);
const auto rotate = [&](const QPointF &v, float64 s, float64 c) { const auto rotate = [&](const QPointF &v, float64 s, float64 c) {
@@ -0,0 +1,660 @@
/*
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 "editor/scene/scene_item_text.h"
#include "editor/scene/scene.h"
#include "editor/scene/scene_emoji_document.h"
#include "lang/lang_keys.h"
#include "ui/emoji_config.h"
#include "ui/painter.h"
#include "ui/widgets/popup_menu.h"
#include "styles/style_editor.h"
#include "styles/style_menu_icons.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextLayout>
#include <QTextOption>
namespace Editor {
namespace {
constexpr auto kPaddingFactor = 0.4;
constexpr auto kMaxWidthFactor = 0.8;
constexpr auto kMinContentWidth = 20;
constexpr auto kBrightnessFramedThreshold = 0.721;
constexpr auto kBrightnessSemiTransparentThreshold = 0.25;
constexpr auto kSemiTransparentAlpha = 0x99;
constexpr auto kCornerRadiusFactor = 1. / 3.;
constexpr auto kLinePadHFactor = 1. / 3.;
constexpr auto kLinePadVFactor = 1. / 8.;
constexpr auto kMergeRadiusFactor = 1.5;
constexpr auto kLineShiftFactor = 1. / 7.;
struct LayoutMetrics {
int contentWidth = 0;
int contentHeight = 0;
int padding = 0;
int textMaxWidth = 0;
};
QFont TextFont(float64 fontSize) {
auto font = QFont();
font.setPixelSize(std::max(int(fontSize), 1));
font.setWeight(QFont::DemiBold);
return font;
}
float64 ComputeBrightness(const QColor &color) {
return (color.red() * 0.2126
+ color.green() * 0.7152
+ color.blue() * 0.0722) / 255.;
}
LayoutMetrics ComputeMetrics(
const QString &text,
float64 fontSize,
const QSize &imageSize,
TextStyle style) {
const auto hasBackground = (style == TextStyle::Framed)
|| (style == TextStyle::SemiTransparent);
const auto padding = hasBackground ? int(fontSize * kPaddingFactor) : 0;
const auto shortSide = std::min(imageSize.width(), imageSize.height());
const auto textMaxWidth = std::max(
int(shortSide * kMaxWidthFactor) - 2 * padding,
kMinContentWidth);
const auto font = TextFont(fontSize);
auto processedText = text;
processedText.replace('\n', QChar::LineSeparator);
auto option = QTextOption();
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
auto layout = QTextLayout(processedText, font);
layout.setTextOption(option);
layout.beginLayout();
auto totalHeight = 0.;
auto maxWidth = 0.;
while (true) {
auto line = layout.createLine();
if (!line.isValid()) {
break;
}
line.setLineWidth(textMaxWidth);
line.setPosition(QPointF(0, totalHeight));
totalHeight += line.height();
maxWidth = std::max(maxWidth, float64(line.naturalTextWidth()));
}
layout.endLayout();
return {
.contentWidth = std::max(int(std::ceil(maxWidth)), kMinContentWidth),
.contentHeight = int(std::ceil(totalHeight)),
.padding = padding,
.textMaxWidth = textMaxWidth,
};
}
struct LineRect {
float64 left = 0;
float64 top = 0;
float64 right = 0;
float64 bottom = 0;
[[nodiscard]] float64 width() const { return right - left; }
};
QPainterPath BuildConnectedBackground(
const QTextLayout &layout,
int contentWidth,
int padding,
float64 fontSize) {
const auto linePadH = fontSize * kLinePadHFactor;
const auto linePadV = fontSize * kLinePadVFactor;
const auto cornerRadius = fontSize * kCornerRadiusFactor;
const auto mergeRadius = cornerRadius * kMergeRadiusFactor;
const auto centerX = padding + contentWidth / 2.;
auto rects = std::vector<LineRect>();
for (auto i = 0; i < layout.lineCount(); ++i) {
const auto line = layout.lineAt(i);
const auto hw = float64(line.naturalTextWidth()) / 2. + linePadH;
rects.push_back({
.left = centerX - hw,
.top = padding + float64(line.y()) - linePadV,
.right = centerX + hw,
.bottom = padding + float64(line.y() + line.height()) + linePadV,
});
}
if (rects.empty()) {
return {};
}
if (rects.size() == 1) {
auto path = QPainterPath();
const auto &r = rects[0];
path.addRoundedRect(
QRectF(r.left, r.top, r.width(), r.bottom - r.top),
cornerRadius,
cornerRadius);
return path;
}
for (auto i = 1; i < int(rects.size()); ++i) {
rects[i - 1].bottom = rects[i].top;
}
for (auto i = 1; i < int(rects.size()); ++i) {
auto traceback = false;
if (std::abs(rects[i - 1].left - rects[i].left) < mergeRadius) {
const auto v = std::min(rects[i - 1].left, rects[i].left);
rects[i - 1].left = rects[i].left = v;
traceback = true;
}
if (std::abs(rects[i - 1].right - rects[i].right) < mergeRadius) {
const auto v = std::max(rects[i - 1].right, rects[i].right);
rects[i - 1].right = rects[i].right = v;
traceback = true;
}
if (traceback) {
for (auto j = i; j >= 1; --j) {
if (std::abs(rects[j - 1].left - rects[j].left)
< mergeRadius) {
const auto v = std::min(
rects[j - 1].left,
rects[j].left);
rects[j - 1].left = rects[j].left = v;
}
if (std::abs(rects[j - 1].right - rects[j].right)
< mergeRadius) {
const auto v = std::max(
rects[j - 1].right,
rects[j].right);
rects[j - 1].right = rects[j].right = v;
}
}
}
}
struct V { float64 x, y; };
auto verts = std::vector<V>();
verts.push_back({ rects[0].left, rects[0].top });
verts.push_back({ rects[0].right, rects[0].top });
for (auto i = 1; i < int(rects.size()); ++i) {
if (std::abs(rects[i].right - rects[i - 1].right) > 0.5) {
verts.push_back({ rects[i - 1].right, rects[i].top });
verts.push_back({ rects[i].right, rects[i].top });
}
}
const auto last = int(rects.size()) - 1;
verts.push_back({ rects[last].right, rects[last].bottom });
verts.push_back({ rects[last].left, rects[last].bottom });
for (auto i = last - 1; i >= 0; --i) {
if (std::abs(rects[i].left - rects[i + 1].left) > 0.5) {
verts.push_back({ rects[i + 1].left, rects[i + 1].top });
verts.push_back({ rects[i].left, rects[i + 1].top });
}
}
auto path = QPainterPath();
const auto n = int(verts.size());
for (auto i = 0; i < n; ++i) {
const auto &prev = verts[(i + n - 1) % n];
const auto &curr = verts[i];
const auto &next = verts[(i + 1) % n];
const auto dx1 = curr.x - prev.x;
const auto dy1 = curr.y - prev.y;
const auto len1 = std::sqrt(dx1 * dx1 + dy1 * dy1);
const auto dx2 = next.x - curr.x;
const auto dy2 = next.y - curr.y;
const auto len2 = std::sqrt(dx2 * dx2 + dy2 * dy2);
if (len1 < 0.1 || len2 < 0.1) {
if (i == 0) {
path.moveTo(curr.x, curr.y);
} else {
path.lineTo(curr.x, curr.y);
}
continue;
}
const auto r = std::min({
cornerRadius,
len1 / 2.,
len2 / 2.,
});
const auto bx = curr.x - dx1 / len1 * r;
const auto by = curr.y - dy1 / len1 * r;
const auto ax = curr.x + dx2 / len2 * r;
const auto ay = curr.y + dy2 / len2 * r;
if (i == 0) {
path.moveTo(bx, by);
} else {
path.lineTo(bx, by);
}
path.quadTo(curr.x, curr.y, ax, ay);
}
path.closeSubpath();
return path;
}
} // namespace
QColor EffectiveTextColor(const QColor &color, TextStyle style) {
if (style != TextStyle::Framed) {
return color;
}
return (ComputeBrightness(color) >= kBrightnessFramedThreshold)
? QColor(0, 0, 0)
: QColor(255, 255, 255);
}
ItemText::ItemText(
const QString &text,
const QColor &color,
float64 fontSize,
TextStyle style,
const QSize &imageSize,
ItemBase::Data data)
: ItemBase(std::move(data))
, _text(text)
, _color(color)
, _fontSize(fontSize)
, _textStyle(style)
, _imageSize(imageSize) {
renderContent();
}
void ItemText::renderContent() {
if (_text.isEmpty()) {
_pixmap = QPixmap();
setAspectRatio(1.);
return;
}
const auto m = ComputeMetrics(_text, _fontSize, _imageSize, _textStyle);
const auto pixWidth = m.contentWidth + 2 * m.padding;
const auto pixHeight = m.contentHeight + 2 * m.padding;
const auto font = TextFont(_fontSize);
auto processedText = _text;
processedText.replace('\n', QChar::LineSeparator);
auto option = QTextOption();
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
auto layout = QTextLayout(processedText, font);
layout.setTextOption(option);
struct EmojiPos {
int start = 0;
int length = 0;
EmojiPtr emoji = nullptr;
};
auto emojiFormats = QVector<QTextLayout::FormatRange>();
auto emojiPositions = std::vector<EmojiPos>();
{
auto pos = 0;
const auto begin = processedText.constData();
const auto end = begin + processedText.size();
while (pos < processedText.size()) {
auto emojiLen = 0;
const auto emoji = Ui::Emoji::Find(
begin + pos,
end,
&emojiLen);
if (emoji && emojiLen > 0) {
auto fmt = QTextCharFormat();
fmt.setForeground(QColor(0, 0, 0, 0));
emojiFormats.append({ pos, emojiLen, fmt });
emojiPositions.push_back({ pos, emojiLen, emoji });
pos += emojiLen;
} else {
++pos;
}
}
}
layout.setFormats(emojiFormats);
layout.beginLayout();
auto y = 0.;
while (true) {
auto line = layout.createLine();
if (!line.isValid()) {
break;
}
line.setLineWidth(m.textMaxWidth);
line.setPosition(QPointF(0, y));
y += line.height();
}
layout.endLayout();
auto textColor = _color;
auto bgColor = QColor(Qt::transparent);
const auto brightness = ComputeBrightness(_color);
const auto hasBackground =
(_textStyle == TextStyle::Framed)
|| (_textStyle == TextStyle::SemiTransparent);
switch (_textStyle) {
case TextStyle::Framed:
bgColor = _color;
textColor = (brightness >= kBrightnessFramedThreshold)
? QColor(0, 0, 0)
: QColor(255, 255, 255);
break;
case TextStyle::SemiTransparent:
bgColor = (brightness >= kBrightnessSemiTransparentThreshold)
? QColor(0, 0, 0, kSemiTransparentAlpha)
: QColor(255, 255, 255, kSemiTransparentAlpha);
break;
case TextStyle::Plain:
break;
}
const auto dpr = style::DevicePixelRatio();
auto pixmap = QPixmap(QSize(pixWidth, pixHeight) * dpr);
pixmap.setDevicePixelRatio(dpr);
pixmap.fill(Qt::transparent);
{
auto p = QPainter(&pixmap);
auto hq = PainterHighQualityEnabler(p);
if (hasBackground) {
const auto bgPath = BuildConnectedBackground(
layout,
m.contentWidth,
m.padding,
_fontSize);
if (_textStyle == TextStyle::SemiTransparent) {
auto opaque = bgColor;
opaque.setAlpha(255);
auto mask = QPixmap(pixmap.size());
mask.setDevicePixelRatio(dpr);
mask.fill(Qt::transparent);
{
auto mp = QPainter(&mask);
auto mhq = PainterHighQualityEnabler(mp);
mp.setPen(Qt::NoPen);
mp.setBrush(opaque);
mp.drawPath(bgPath);
}
p.setOpacity(bgColor.alphaF());
p.drawPixmap(0, 0, mask);
p.setOpacity(1.0);
} else {
p.setPen(Qt::NoPen);
p.setBrush(bgColor);
p.drawPath(bgPath);
}
}
const auto lineShift = _fontSize * kLineShiftFactor;
const auto lineCount = layout.lineCount();
p.setPen(textColor);
for (auto i = 0; i < lineCount; ++i) {
const auto line = layout.lineAt(i);
const auto xOffset =
(m.contentWidth - line.naturalTextWidth()) / 2.;
const auto yShift = (i < lineCount - 1) ? -lineShift : 0.;
line.draw(
&p,
QPointF(m.padding + xOffset, m.padding + yShift));
}
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
const auto factor = style::DevicePixelRatio();
const auto source = Ui::Emoji::GetSizeLarge();
const auto sourceLogical = source / float64(factor);
const auto emojiSize = float64(QFontMetrics(font).height());
const auto emojiScale = emojiSize / sourceLogical;
for (const auto &ep : emojiPositions) {
auto lineIndex = -1;
for (auto i = 0; i < lineCount; ++i) {
const auto line = layout.lineAt(i);
const auto lineStart = line.textStart();
const auto lineEnd = lineStart + line.textLength();
if (ep.start >= lineStart && ep.start < lineEnd) {
lineIndex = i;
break;
}
}
if (lineIndex < 0) {
continue;
}
const auto line = layout.lineAt(lineIndex);
const auto lineStart = line.textStart();
const auto lineEnd = lineStart + line.textLength();
const auto drawEnd = std::min(ep.start + ep.length, lineEnd);
const auto xOffset =
(m.contentWidth - line.naturalTextWidth()) / 2.;
const auto yShift = (lineIndex < lineCount - 1)
? -lineShift
: 0.;
const auto x = line.cursorToX(ep.start);
const auto nextX = line.cursorToX(drawEnd);
const auto glyphWidth = float64(nextX - x);
const auto drawX = m.padding
+ xOffset
+ x
+ (glyphWidth - emojiSize) / 2.;
const auto drawY = m.padding
+ yShift
+ line.y()
+ (line.height() - emojiSize) / 2.;
p.save();
p.translate(drawX, drawY);
p.scale(emojiScale, emojiScale);
Ui::Emoji::Draw(p, ep.emoji, source, 0, 0);
p.restore();
}
}
_pixmap = std::move(pixmap);
const auto handleMargin = std::max(
innerRect().width() - contentRect().width(),
0.);
setAspectRatio(
(pixHeight + handleMargin) / float64(pixWidth + handleMargin));
}
QSize ItemText::computeContentSize(
const QString &text,
float64 fontSize,
const QSize &imageSize,
TextStyle style) {
if (text.isEmpty()) {
return {};
}
auto processedText = text;
processedText.replace('\n', QChar::LineSeparator);
const auto m = ComputeMetrics(processedText, fontSize, imageSize, style);
return QSize(
m.contentWidth + 2 * m.padding,
m.contentHeight + 2 * m.padding);
}
void ItemText::paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *w) {
if (!_pixmap.isNull()) {
const auto rect = contentRect();
const auto pixmapSize = QSizeF(
_pixmap.size() / style::DevicePixelRatio()
).scaled(rect.size(), Qt::KeepAspectRatio);
const auto resultRect = QRectF(
rect.topLeft(),
pixmapSize
).translated(
(rect.width() - pixmapSize.width()) / 2.,
(rect.height() - pixmapSize.height()) / 2.);
if (flipped()) {
p->save();
const auto center = resultRect.center();
p->translate(center);
p->scale(-1, 1);
p->translate(-center);
p->drawPixmap(resultRect.toRect(), _pixmap);
p->restore();
} else {
p->drawPixmap(resultRect.toRect(), _pixmap);
}
}
ItemBase::paint(p, option, w);
}
int ItemText::type() const {
return Type;
}
const QString &ItemText::text() const {
return _text;
}
void ItemText::setText(const QString &text) {
_text = text;
renderContent();
update();
}
const QColor &ItemText::color() const {
return _color;
}
void ItemText::setColor(const QColor &color) {
_color = color;
renderContent();
update();
}
float64 ItemText::fontSize() const {
return _fontSize;
}
float64 ItemText::editScale() const {
const auto natural = computeContentSize(
_text,
_fontSize,
_imageSize,
_textStyle);
if (natural.width() <= 0) {
return 1.;
}
return size() / natural.width();
}
TextStyle ItemText::textStyle() const {
return _textStyle;
}
void ItemText::setTextStyle(TextStyle style) {
_textStyle = style;
renderContent();
update();
}
void ItemText::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
if (const auto s = static_cast<Scene*>(scene())) {
s->startTextEditing(this);
}
}
void ItemText::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
if (scene()) {
scene()->clearSelection();
setSelected(true);
}
_contextMenu = base::make_unique_q<Ui::PopupMenu>(
nullptr,
st::popupMenuWithIcons);
const auto add = [&](const QString &text, TextStyle style) {
const auto checked = (_textStyle == style);
auto action = _contextMenu->addAction(text, [=] {
setTextStyle(style);
});
if (checked) {
action->setChecked(true);
}
};
add(tr::lng_photo_editor_text_style_plain(tr::now), TextStyle::Plain);
add(
tr::lng_photo_editor_text_style_framed(tr::now),
TextStyle::Framed);
add(
tr::lng_photo_editor_text_style_semi_transparent(tr::now),
TextStyle::SemiTransparent);
_contextMenu->addSeparator();
_contextMenu->addAction(
tr::lng_photo_editor_menu_delete(tr::now),
[=] { actionDelete(); },
&st::menuIconDelete);
_contextMenu->addAction(
tr::lng_photo_editor_menu_duplicate(tr::now),
[=] { actionDuplicate(); },
&st::menuIconCopy);
_contextMenu->popup(event->screenPos());
}
void ItemText::performFlip() {
update();
}
std::shared_ptr<ItemBase> ItemText::duplicate(ItemBase::Data data) const {
return std::make_shared<ItemText>(
_text,
_color,
_fontSize,
_textStyle,
_imageSize,
std::move(data));
}
void ItemText::save(SaveState state) {
ItemBase::save(state);
auto &saved = (state == SaveState::Keep) ? _keepedState : _savedState;
saved = {
.text = _text,
.color = _color,
.fontSize = _fontSize,
.textStyle = _textStyle,
};
}
void ItemText::restore(SaveState state) {
if (!hasState(state)) {
return;
}
const auto &saved = (state == SaveState::Keep) ? _keepedState : _savedState;
_text = saved.text;
_color = saved.color;
_fontSize = saved.fontSize;
_textStyle = saved.textStyle;
renderContent();
ItemBase::restore(state);
}
} // namespace Editor
@@ -0,0 +1,93 @@
/*
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/unique_qptr.h"
#include "editor/scene/scene_item_base.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Editor {
enum class TextStyle : uchar {
Framed,
SemiTransparent,
Plain,
};
[[nodiscard]] QColor EffectiveTextColor(const QColor &color, TextStyle style);
class ItemText : public ItemBase {
public:
enum { Type = ItemBase::Type + 2 };
ItemText(
const QString &text,
const QColor &color,
float64 fontSize,
TextStyle style,
const QSize &imageSize,
ItemBase::Data data);
void paint(
QPainter *p,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
int type() const override;
[[nodiscard]] const QString &text() const;
void setText(const QString &text);
[[nodiscard]] const QColor &color() const;
void setColor(const QColor &color);
[[nodiscard]] float64 fontSize() const;
[[nodiscard]] TextStyle textStyle() const;
void setTextStyle(TextStyle style);
[[nodiscard]] float64 editScale() const;
[[nodiscard]] static QSize computeContentSize(
const QString &text,
float64 fontSize,
const QSize &imageSize,
TextStyle style);
void save(SaveState state) override;
void restore(SaveState state) override;
protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
void performFlip() override;
std::shared_ptr<ItemBase> duplicate(ItemBase::Data data) const override;
private:
void renderContent();
QString _text;
QColor _color;
float64 _fontSize;
TextStyle _textStyle = TextStyle::Plain;
QSize _imageSize;
QPixmap _pixmap;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
struct SavedText {
QString text;
QColor color;
float64 fontSize = 0.;
TextStyle textStyle = TextStyle::Plain;
};
SavedText _savedState, _keepedState;
};
} // namespace Editor
@@ -100,6 +100,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_attached_stickers.h" #include "api/api_attached_stickers.h"
#include "api/api_suggest_post.h" #include "api/api_suggest_post.h"
#include "api/api_stickers_creator.h"
#include "api/api_toggling_media.h" #include "api/api_toggling_media.h"
#include "api/api_who_reacted.h" #include "api/api_who_reacted.h"
#include "api/api_views.h" #include "api/api_views.h"
@@ -3335,6 +3336,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] { _menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] {
showStickerPackInfo(document); showStickerPackInfo(document);
}, &st::menuIconStickers); }, &st::menuIconStickers);
} else {
Api::AddAddToStickerSetAction(
Ui::Menu::CreateAddActionCallback(_menu),
_controller->uiShow(),
document);
} }
{ {
const auto isFaved = session->data().stickers().isFaved(document); const auto isFaved = session->data().stickers().isFaved(document);
+12 -2
Ver Arquivo
@@ -1392,9 +1392,14 @@ void HistoryWidget::sendTextAsFile(
_peer, _peer,
Api::SendType::Normal, Api::SendType::Normal,
sendMenuDetails()); sendMenuDetails());
box->setReplyTo(replyTo());
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options,
FullReplyTo currentReplyTo) {
if (!currentReplyTo.messageId && replyTo().messageId) {
cancelReply();
}
sendingFilesConfirmed(std::move(bundle), options); sendingFilesConfirmed(std::move(bundle), options);
})); }));
box->setCancelledCallback(crl::guard(this, [=] { box->setCancelledCallback(crl::guard(this, [=] {
@@ -6820,10 +6825,12 @@ bool HistoryWidget::confirmSendingFiles(
_peer, _peer,
Api::SendType::Normal, Api::SendType::Normal,
sendMenuDetails()); sendMenuDetails());
box->setReplyTo(replyTo());
_field->setTextWithTags({}); _field->setTextWithTags({});
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options,
FullReplyTo currentReplyTo) {
if (bundle->way.asVoice && !bundle->groups.empty() if (bundle->way.asVoice && !bundle->groups.empty()
&& !bundle->groups.front().list.files.empty()) { && !bundle->groups.front().list.files.empty()) {
const auto &front = bundle->groups.front().list.files.front(); const auto &front = bundle->groups.front().list.files.front();
@@ -6848,6 +6855,9 @@ bool HistoryWidget::confirmSendingFiles(
file.close(); file.close();
} }
} }
if (!currentReplyTo.messageId && replyTo().messageId) {
cancelReply();
}
sendingFilesConfirmed(std::move(bundle), options); sendingFilesConfirmed(std::move(bundle), options);
})); }));
box->setCancelledCallback(crl::guard(this, [=] { box->setCancelledCallback(crl::guard(this, [=] {
@@ -3396,6 +3396,16 @@ void ComposeControls::fireSendTextAsFile(
? Api::SendType::ScheduledToUser ? Api::SendType::ScheduledToUser
: Api::SendType::Scheduled) : Api::SendType::Scheduled)
: Api::SendType::Normal; : Api::SendType::Normal;
auto confirmed = [=, callback = _sendAsFileConfirmed](
std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options,
FullReplyTo replyTo) {
if (!replyTo.messageId
&& replyingToMessage().messageId) {
cancelReplyMessage();
}
callback(std::move(bundle), options);
};
_show->show(Box<SendFilesBox>(SendFilesBoxDescriptor{ _show->show(Box<SendFilesBox>(SendFilesBoxDescriptor{
.show = _show, .show = _show,
.list = Ui::PrepareTextAsFile(fileText), .list = Ui::PrepareTextAsFile(fileText),
@@ -3406,8 +3416,9 @@ void ComposeControls::fireSendTextAsFile(
.sendType = sendType, .sendType = sendType,
.sendMenuDetails = _sendMenuDetails, .sendMenuDetails = _sendMenuDetails,
.stOverride = &_st, .stOverride = &_st,
.confirmed = _sendAsFileConfirmed, .confirmed = std::move(confirmed),
.cancelled = std::move(restoreText), .cancelled = std::move(restoreText),
.replyTo = replyingToMessage(),
})); }));
} }
@@ -1192,10 +1192,16 @@ bool ChatWidget::confirmSendingFiles(
_peer, _peer,
Api::SendType::Normal, Api::SendType::Normal,
sendMenuDetails()); sendMenuDetails());
box->setReplyTo(_composeControls->replyingToMessage());
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options,
FullReplyTo currentReplyTo) {
if (!currentReplyTo.messageId
&& _composeControls->replyingToMessage().messageId) {
_composeControls->cancelReplyMessage();
}
sendingFilesConfirmed(std::move(bundle), options); sendingFilesConfirmed(std::move(bundle), options);
})); }));
box->setCancelledCallback(_composeControls->restoreTextCallback( box->setCancelledCallback(_composeControls->restoreTextCallback(
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_ringtones.h" #include "api/api_ringtones.h"
#include "api/api_transcribes.h" #include "api/api_transcribes.h"
#include "api/api_who_reacted.h" #include "api/api_who_reacted.h"
#include "api/api_stickers_creator.h"
#include "api/api_toggling_media.h" // Api::ToggleFavedSticker #include "api/api_toggling_media.h" // Api::ToggleFavedSticker
#include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_key_modifiers.h"
#include "base/unixtime.h" #include "base/unixtime.h"
@@ -36,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_widget.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/menu/menu_action.h" #include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/menu/menu_common.h" #include "ui/widgets/menu/menu_common.h"
#include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/menu/menu_separator.h" #include "ui/widgets/menu/menu_separator.h"
@@ -295,6 +297,12 @@ void AddDocumentActions(
[=] { ShowStickerPackInfo(document, list); }, [=] { ShowStickerPackInfo(document, list); },
&st::menuIconStickers); &st::menuIconStickers);
} }
if (document->sticker() && !document->sticker()->set) {
Api::AddAddToStickerSetAction(
Ui::Menu::CreateAddActionCallback(menu),
controller->uiShow(),
document);
}
if (document->sticker()) { if (document->sticker()) {
const auto isFaved = document->owner().stickers().isFaved(document); const auto isFaved = document->owner().stickers().isFaved(document);
menu->addAction( menu->addAction(
@@ -63,7 +63,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kSummarizeThreshold = 512;
constexpr auto kPlayStatusLimit = 2; constexpr auto kPlayStatusLimit = 2;
constexpr auto kMaxWidth = (1 << 16) - 1; constexpr auto kMaxWidth = (1 << 16) - 1;
constexpr auto kMaxNiceToReadLines = 6; constexpr auto kMaxNiceToReadLines = 6;
@@ -2327,11 +2326,10 @@ void Message::paintText(
}); });
} }
const auto realWidth = textRealWidth();
auto highlightRequest = context.computeHighlightCache(); auto highlightRequest = context.computeHighlightCache();
text().draw(p, { text().draw(p, {
.position = trect.topLeft(), .position = trect.topLeft(),
.availableWidth = realWidth ? realWidth : trect.width(), .availableWidth = std::max(textRealWidth(), trect.width()),
.palette = &stm->textPalette, .palette = &stm->textPalette,
.pre = stm->preCache.get(), .pre = stm->preCache.get(),
.blockquote = context.quoteCache( .blockquote = context.quoteCache(
@@ -3650,7 +3648,7 @@ bool Message::getStateText(
if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
*outResult = TextState(item, text().getState( *outResult = TextState(item, text().getState(
point - trect.topLeft(), point - trect.topLeft(),
trect.width(), std::max(textRealWidth(), trect.width()),
request.forText())); request.forText()));
if (outResult->link if (outResult->link
&& IsRippleLink(outResult->link) && IsRippleLink(outResult->link)
@@ -4225,6 +4223,8 @@ int Message::bubbleTextualWidth() const {
} }
} }
_bubbleTextualWidthCache = right; _bubbleTextualWidthCache = right;
[[maybe_unused]] const auto ensureRightCache
= textHeightFor(bubbleTextWidth(right));
} }
} }
} }
@@ -32,8 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kPremiumToastDuration = 5 * crl::time(1000);
[[nodiscard]] not_null<Ui::AbstractButton*> MakeUndoButton( [[nodiscard]] not_null<Ui::AbstractButton*> MakeUndoButton(
not_null<QWidget*> parent, not_null<QWidget*> parent,
int width, int width,
@@ -575,7 +575,8 @@ bool ScheduledWidget::confirmSendingFiles(
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options,
FullReplyTo) {
sendingFilesConfirmed(std::move(bundle), options); sendingFilesConfirmed(std::move(bundle), options);
})); }));
box->setCancelledCallback(_composeControls->restoreTextCallback( box->setCancelledCallback(_composeControls->restoreTextCallback(
@@ -605,7 +605,7 @@ QSize Document::countCurrentSize(int newWidth) {
+ st::mediaUnreadSkip) + st::mediaUnreadSkip)
+ (thumbedWidth + statusWidth) + (thumbedWidth + statusWidth)
+ st.thumbSkip + st.thumbSkip
+ (_realParent->hasUnreadMediaFlag() + (_realParent->isUnreadMedia()
? st::mediaUnreadSkip + st::mediaUnreadSize ? st::mediaUnreadSkip + st::mediaUnreadSize
: 0) : 0)
+ _parent->bottomInfoFirstLineWidth() + _parent->bottomInfoFirstLineWidth()
@@ -953,7 +953,7 @@ void Document::draw(
p.setPen(stm->mediaFg); p.setPen(stm->mediaFg);
p.drawTextLeft(nameleft, statustop, width, statusText); p.drawTextLeft(nameleft, statustop, width, statusText);
if (_realParent->hasUnreadMediaFlag()) { if (_realParent->isUnreadMedia()) {
auto w = st::normalFont->width(statusText); auto w = st::normalFont->width(statusText);
if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
@@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include <QSvgRenderer> #include <QSvgRenderer>
#include <QtWidgets/QApplication>
namespace HistoryView { namespace HistoryView {
namespace { namespace {
@@ -68,6 +69,8 @@ namespace {
constexpr auto kMaxGifForwardedBarLines = 4; constexpr auto kMaxGifForwardedBarLines = 4;
constexpr auto kUseNonBlurredThreshold = 240; constexpr auto kUseNonBlurredThreshold = 240;
constexpr auto kMaxInlineArea = 1920 * 1080; constexpr auto kMaxInlineArea = 1920 * 1080;
constexpr auto kSeekAnimationDuration = crl::time(200);
constexpr auto kSeekTrackOpacity = 0.2;
[[nodiscard]] int GifMaxStatusWidth(not_null<DocumentData*> document) { [[nodiscard]] int GifMaxStatusWidth(not_null<DocumentData*> document) {
auto result = st::normalFont->width( auto result = st::normalFont->width(
@@ -197,6 +200,12 @@ Gif::Gif(
setStatusSize(Ui::FileStatusSizeReady); setStatusSize(Ui::FileStatusSizeReady);
if (_data->isVideoMessage() && !_parent->data()->media()->ttlSeconds()) {
_seekl = std::make_shared<VoiceSeekClickHandler>(
_data,
[](FullMsgId) {});
}
if (_spoiler) { if (_spoiler) {
createSpoilerLink(_spoiler.get()); createSpoilerLink(_spoiler.get());
} }
@@ -539,6 +548,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
== PaintContext::SkipDrawingParts::Content; == PaintContext::SkipDrawingParts::Content;
const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover); const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover);
if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) { if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) {
if (!_seekLastFrame.isNull()) {
_seekLastFrame = QImage();
}
auto paused = context.paused || !shouldBePlaying; auto paused = context.paused || !shouldBePlaying;
auto request = ::Media::Streaming::FrameRequest{ auto request = ::Media::Streaming::FrameRequest{
.outer = QSize(usew, painth) * style::DevicePixelRatio(), .outer = QSize(usew, painth) * style::DevicePixelRatio(),
@@ -574,39 +586,25 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
const auto frame = streamed->frameWithInfo(request); const auto frame = streamed->frameWithInfo(request);
p.drawImage(rthumb, frame.image); p.drawImage(rthumb, frame.image);
if (_seeking) {
_seekLastFrame = frame.image;
}
if (!paused) { if (!paused) {
streamed->markFrameShown(); streamed->markFrameShown();
} }
} }
} else if (!_seekLastFrame.isNull()
if (const auto playback = videoPlayback()) { && !skipDrawingContent
const auto value = playback->value(); && !fullHiddenBySpoiler) {
if (value > 0.) { p.drawImage(rthumb, _seekLastFrame);
auto pen = st->historyVideoMessageProgressFg()->p;
const auto was = p.pen();
pen.setWidth(st::radialLine);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
p.setOpacity(st::historyVideoMessageProgressOpacity);
const auto from = arc::kQuarterLength;
const auto len = std::round(arc::kFullLength
* (inTTLViewer ? (1. - value) : -value));
const auto stepInside = st::radialLine / 2;
{
auto hq = PainterHighQualityEnabler(p);
p.drawArc(rthumb - Margins(stepInside), from, len);
}
p.setPen(was);
p.setOpacity(1.);
}
}
} else if (!skipDrawingContent && !fullHiddenBySpoiler) { } else if (!skipDrawingContent && !fullHiddenBySpoiler) {
ensureDataMediaCreated(); ensureDataMediaCreated();
validateThumbCache({ usew, painth }, isRound, rounding); validateThumbCache({ usew, painth }, isRound, rounding);
p.drawImage(rthumb, _thumbCache); p.drawImage(rthumb, _thumbCache);
} }
if (isRound) {
paintRoundPlaybackProgress(p, context, rthumb, inTTLViewer);
}
if (!isRound) { if (!isRound) {
paintTimestampMark(p, rthumb, rounding); paintTimestampMark(p, rthumb, rounding);
} }
@@ -736,7 +734,11 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
} else if (!skipDrawingSurrounding) { } else if (!skipDrawingSurrounding) {
if (isRound) { if (isRound) {
const auto mediaUnread = item->hasUnreadMediaFlag(); const auto mediaUnread = item->hasUnreadMediaFlag();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); const auto statusText = _seeking
? Ui::FormatDurationText(1 + int64(base::SafeRound(
(1. - _seekingCurrent) * _data->duration() / 1000.)))
: _statusText;
auto statusW = st::normalFont->width(statusText) + 2 * st::msgDateImgPadding.x();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y(); auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y();
@@ -746,7 +748,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), sti->msgServiceBg, sti->msgServiceBgCornersSmall); Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), sti->msgServiceBg, sti->msgServiceBgCornersSmall);
p.setFont(st::normalFont); p.setFont(st::normalFont);
p.setPen(st->msgServiceFg()); p.setPen(st->msgServiceFg());
p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); p.drawTextLeft(statusX, statusY, width(), statusText, statusW - 2 * st::msgDateImgPadding.x());
if (mediaUnread) { if (mediaUnread) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st->msgServiceFg()); p.setBrush(st->msgServiceFg());
@@ -959,6 +961,62 @@ void Gif::paintTimestampMark(
p.restore(); p.restore();
} }
void Gif::paintRoundPlaybackProgress(
Painter &p,
const PaintContext &context,
QRect rthumb,
bool inTTLViewer) const {
const auto st = context.st;
const auto playback = videoPlayback();
const auto seekAmount = _seekAnimation.value(_seeking ? 1. : 0.);
const auto value = _seeking
? _seekingCurrent
: playback
? playback->value()
: (seekAmount > 0.)
? _seekingCurrent
: 0.;
if (value <= 0. && seekAmount <= 0.) {
return;
}
auto pen = st->historyVideoMessageProgressFg()->p;
const auto was = p.pen();
pen.setWidth(st::radialLine);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
const auto from = arc::kQuarterLength;
const auto normalInset = 1.5 * st::radialLine;
const auto seekInset = st::historyVideoMessageSeekInset;
const auto stepInside = normalInset
+ (seekInset - normalInset) * seekAmount;
const auto arcRect = QRectF(rthumb) - Margins(stepInside);
auto hq = PainterHighQualityEnabler(p);
if (seekAmount > 0.) {
p.setOpacity(kSeekTrackOpacity * seekAmount);
p.drawArc(arcRect, 0, arc::kFullLength);
}
p.setOpacity(st::historyVideoMessageProgressOpacity);
const auto len = std::round(arc::kFullLength
* (inTTLViewer ? (1. - value) : -value));
p.drawArc(arcRect, from, len);
if (seekAmount > 0.) {
const auto dotSize = float64(st::historyVideoMessageSeekDotSize);
const auto angle = M_PI / 2. - value * 2. * M_PI;
const auto radius = arcRect.width() / 2.;
const auto center = arcRect.center();
const auto cx = center.x() + radius * cos(angle);
const auto cy = center.y() - radius * sin(angle);
p.setOpacity(seekAmount);
p.setPen(Qt::NoPen);
p.setBrush(st->historyVideoMessageProgressFg());
p.drawEllipse(QPointF(cx, cy), dotSize / 2., dotSize / 2.);
}
p.setBrush(Qt::NoBrush);
p.setPen(was);
p.setOpacity(1.);
}
void Gif::drawSpoilerTag( void Gif::drawSpoilerTag(
Painter &p, Painter &p,
QRect rthumb, QRect rthumb,
@@ -1289,13 +1347,17 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
} }
if (QRect(usex + paintx, painty, usew, painth).contains(point)) { if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
ensureDataMediaCreated(); ensureDataMediaCreated();
result.link = (_spoiler && !_spoiler->revealed) if (_spoiler && !_spoiler->revealed) {
? (_sensitiveSpoiler result.link = _sensitiveSpoiler
? spoilerTagLink() ? spoilerTagLink()
: (isRound && _parent->data()->media()->ttlSeconds()) : (isRound && _parent->data()->media()->ttlSeconds())
? _openl // Overriden. ? _openl
: _spoiler->link) : _spoiler->link;
: currentVideoLink(); } else if (_seekl && isRoundSeekable()) {
result.link = _seekl;
} else {
result.link = currentVideoLink();
}
} }
const auto checkBottomInfo = !inWebPage const auto checkBottomInfo = !inWebPage
&& (unwrapped || !bubble || isBubbleBottom()); && (unwrapped || !bubble || isBubbleBottom());
@@ -1362,6 +1424,32 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
void Gif::clickHandlerPressedChanged( void Gif::clickHandlerPressedChanged(
const ClickHandlerPtr &handler, const ClickHandlerPtr &handler,
bool pressed) { bool pressed) {
if (_seekl && handler == _seekl) {
if (pressed && !_seeking) {
_seekPressPoint = QPoint(-1, -1);
if (const auto playback = videoPlayback()) {
_seekingCurrent = playback->value();
}
} else if (!pressed) {
if (_seeking) {
if (isRoundSeekable()) {
::Media::Player::instance()->finishSeeking(
AudioMsgId::Type::Voice,
_seekingCurrent);
}
_seeking = false;
_seekAnimation.start(
[=] { repaint(); },
1.,
0.,
kSeekAnimationDuration);
} else if (_seekPressPoint != QPoint()) {
_seekPressPoint = QPoint();
::Media::Player::instance()->playPauseCancelClicked(
AudioMsgId::Type::Voice);
}
}
}
File::clickHandlerPressedChanged(handler, pressed); File::clickHandlerPressedChanged(handler, pressed);
if (!handler) { if (!handler) {
return; return;
@@ -1374,6 +1462,61 @@ void Gif::clickHandlerPressedChanged(
} }
} }
void Gif::updatePressed(QPoint point) {
if (!_seeking && _seekPressPoint == QPoint()) {
return;
}
const auto item = _parent->data();
auto paintx = 0, painty = 0, paintw = width(), painth = height();
const auto unwrapped = isUnwrapped();
auto usew = paintw, usex = 0;
const auto via = unwrapped ? item->Get<HistoryMessageVia>() : nullptr;
const auto reply = unwrapped ? _parent->Get<Reply>() : nullptr;
const auto forwarded = unwrapped
? item->Get<HistoryMessageForwarded>()
: nullptr;
if (via || reply || forwarded) {
usew = maxWidth() - additionalWidth(reply, via, forwarded);
if (unwrapped && _parent->hasRightLayout()) {
usex = width() - usew;
}
}
accumulate_min(usew, painth);
if (rtl()) usex = width() - usex - usew;
const auto rthumb = QRect(
style::rtlrect(usex + paintx, painty, usew, painth, width()));
if (!_seeking) {
if (_seekPressPoint == QPoint(-1, -1)) {
_seekPressPoint = point;
return;
}
if ((point - _seekPressPoint).manhattanLength()
<= QApplication::startDragDistance()) {
return;
}
_seeking = true;
_seekPressPoint = QPoint();
::Media::Player::instance()->startSeeking(
AudioMsgId::Type::Voice);
_seekAnimation.start(
[=] { repaint(); },
0.,
1.,
kSeekAnimationDuration);
}
const auto center = rthumb.center();
const auto dx = float64(point.x() - center.x());
const auto dy = float64(point.y() - center.y());
const auto angle = atan2(-dy, dx);
_seekingCurrent = std::clamp(
fmod((M_PI / 2. - angle) / (2. * M_PI) + 1., 1.),
0.,
1.);
repaint();
}
bool Gif::fullFeaturedGrouped(RectParts sides) const { bool Gif::fullFeaturedGrouped(RectParts sides) const {
return (sides & RectPart::Left) && (sides & RectPart::Right); return (sides & RectPart::Left) && (sides & RectPart::Right);
} }
@@ -1941,6 +2084,7 @@ void Gif::unloadHeavyPart() {
_spoiler->animation = nullptr; _spoiler->animation = nullptr;
} }
_thumbCache = QImage(); _thumbCache = QImage();
_seekLastFrame = QImage();
_videoThumbnailFrame = nullptr; _videoThumbnailFrame = nullptr;
togglePollingStory(false); togglePollingStory(false);
} }
@@ -1969,6 +2113,19 @@ int Gif::additionalWidth(
return ::Media::Player::instance()->roundVideoStreamed(_parent->data()); return ::Media::Player::instance()->roundVideoStreamed(_parent->data());
} }
bool Gif::isRoundSeekable() const {
if (!activeRoundStreamed()) {
return false;
}
const auto state = ::Media::Player::instance()->getState(
AudioMsgId::Type::Voice);
return (state.id == AudioMsgId(
_data,
_realParent->fullId(),
state.id.externalPlayId()))
&& !::Media::Player::IsStoppedOrStopping(state.state);
}
Gif::Streamed *Gif::activeOwnStreamed() const { Gif::Streamed *Gif::activeOwnStreamed() const {
return (_streamed return (_streamed
&& _streamed->instance.player().ready() && _streamed->instance.player().ready()
@@ -16,6 +16,7 @@ struct HistoryMessageReply;
struct HistoryMessageForwarded; struct HistoryMessageForwarded;
class Painter; class Painter;
class PhotoData; class PhotoData;
class VoiceSeekClickHandler;
namespace Data { namespace Data {
class DocumentMedia; class DocumentMedia;
@@ -65,6 +66,7 @@ public:
void clickHandlerPressedChanged( void clickHandlerPressedChanged(
const ClickHandlerPtr &p, const ClickHandlerPtr &p,
bool pressed) override; bool pressed) override;
void updatePressed(QPoint point) override;
bool uploading() const override; bool uploading() const override;
@@ -151,6 +153,7 @@ private:
Streamed *activeOwnStreamed() const; Streamed *activeOwnStreamed() const;
::Media::Streaming::Instance *activeCurrentStreamed() const; ::Media::Streaming::Instance *activeCurrentStreamed() const;
::Media::View::PlaybackProgress *videoPlayback() const; ::Media::View::PlaybackProgress *videoPlayback() const;
bool isRoundSeekable() const;
void createStreamedPlayer(); void createStreamedPlayer();
void checkStreamedIsStarted() const; void checkStreamedIsStarted() const;
@@ -172,6 +175,11 @@ private:
Painter &p, Painter &p,
QRect rthumb, QRect rthumb,
std::optional<Ui::BubbleRounding> rounding) const; std::optional<Ui::BubbleRounding> rounding) const;
void paintRoundPlaybackProgress(
Painter &p,
const PaintContext &context,
QRect rthumb,
bool inTTLViewer) const;
[[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool needInfoDisplay() const;
[[nodiscard]] bool needCornerStatusDisplay() const; [[nodiscard]] bool needCornerStatusDisplay() const;
@@ -231,12 +239,18 @@ private:
mutable QImage _thumbCache; mutable QImage _thumbCache;
mutable QImage _roundingMask; mutable QImage _roundingMask;
mutable crl::time _videoPosition = 0; mutable crl::time _videoPosition = 0;
std::shared_ptr<VoiceSeekClickHandler> _seekl;
mutable Ui::Animations::Simple _seekAnimation;
float64 _seekingCurrent = 0.;
QPoint _seekPressPoint;
mutable QImage _seekLastFrame;
mutable TimeId _videoTimestamp = 0; mutable TimeId _videoTimestamp = 0;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding; mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbCacheBlurred : 1 = false;
mutable bool _thumbIsEllipse : 1 = false; mutable bool _thumbIsEllipse : 1 = false;
mutable bool _pollingStory : 1 = false; mutable bool _pollingStory : 1 = false;
mutable bool _purchasedPriceTag : 1 = false; mutable bool _purchasedPriceTag : 1 = false;
mutable bool _seeking : 1 = false;
mutable bool _smallGroupPart : 1 = false; mutable bool _smallGroupPart : 1 = false;
const bool _sensitiveSpoiler : 1 = false; const bool _sensitiveSpoiler : 1 = false;
const bool _hasVideoCover : 1 = false; const bool _hasVideoCover : 1 = false;
@@ -201,7 +201,7 @@ QSize Sticker::Size() {
const auto side = std::min(st::maxStickerSize, kMaxSizeFixed); const auto side = std::min(st::maxStickerSize, kMaxSizeFixed);
if (OptionStickerSize.value() > 0) [[unlikely]] { if (OptionStickerSize.value() > 0) [[unlikely]] {
const auto scaled = std::clamp( const auto scaled = std::clamp(
OptionStickerSize.value(), style::ConvertScale(OptionStickerSize.value()),
style::ConvertScale(50), style::ConvertScale(50),
side); side);
return { scaled, scaled }; return { scaled, scaled };
@@ -30,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Info::GlobalMedia { namespace Info::GlobalMedia {
namespace { namespace {
constexpr auto kPerPage = 50;
constexpr auto kPreloadedScreensCount = 4; constexpr auto kPreloadedScreensCount = 4;
constexpr auto kPreloadedScreensCountFull constexpr auto kPreloadedScreensCountFull
= kPreloadedScreensCount + 1 + kPreloadedScreensCount; = kPreloadedScreensCount + 1 + kPreloadedScreensCount;
@@ -2232,11 +2232,14 @@ void ListWidget::mouseActionFinish(
&& (button != Qt::RightButton) && (button != Qt::RightButton)
&& (_mouseAction == MouseAction::PrepareDrag && (_mouseAction == MouseAction::PrepareDrag
|| _mouseAction == MouseAction::PrepareSelect); || _mouseAction == MouseAction::PrepareSelect);
if (_mouseAction == MouseAction::Reordering if (_mouseAction == MouseAction::Reordering) {
|| _mouseAction == MouseAction::PrepareReorder) {
finishReorder(); finishReorder();
return; return;
} }
if (_mouseAction == MouseAction::PrepareReorder) {
_reorderState = {};
_mouseAction = MouseAction::PrepareDrag;
}
const auto needSelectionToggle = simpleSelectionChange && selectionMode; const auto needSelectionToggle = simpleSelectionChange && selectionMode;
const auto needSelectionClear = simpleSelectionChange const auto needSelectionClear = simpleSelectionChange
&& hasSelectedText(); && hasSelectedText();
+1
Ver Arquivo
@@ -462,6 +462,7 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
_mtp->setStateChangedHandler([=](MTP::ShiftedDcId dc, int32 state) { _mtp->setStateChangedHandler([=](MTP::ShiftedDcId dc, int32 state) {
if (dc == _mtp->mainDcId()) { if (dc == _mtp->mainDcId()) {
Core::App().settings().proxy().connectionTypeChangesNotify(); Core::App().settings().proxy().connectionTypeChangesNotify();
Core::App().checkProxyRotation(this, state);
} }
}); });
_mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) { _mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) {
@@ -14,7 +14,6 @@ namespace Media::Audio {
namespace { namespace {
constexpr auto kMaxDuration = 3 * crl::time(1000); constexpr auto kMaxDuration = 3 * crl::time(1000);
constexpr auto kMaxStreams = 2;
constexpr auto kFrameSize = 4096; constexpr auto kFrameSize = 4096;
[[nodiscard]] QByteArray ConvertAndCut(const QByteArray &bytes) { [[nodiscard]] QByteArray ConvertAndCut(const QByteArray &bytes) {
+2 -1
Ver Arquivo
@@ -27,7 +27,8 @@ enum class OrderMode {
struct VideoQuality { struct VideoQuality {
uint32 manual : 1 = 0; uint32 manual : 1 = 0;
uint32 height : 31 = 0; uint32 height : 30 = 0;
uint32 original : 1 = 0;
friend inline constexpr auto operator<=>( friend inline constexpr auto operator<=>(
VideoQuality, VideoQuality,
@@ -355,7 +355,7 @@ void SettingsButton::setSpeed(float64 speed) {
} }
} }
void SettingsButton::setQuality(int quality) { void SettingsButton::setQuality(Media::VideoQuality quality) {
if (_quality != quality) { if (_quality != quality) {
_quality = quality; _quality = quality;
update(); update();
@@ -437,13 +437,14 @@ void SettingsButton::prepareFrame() {
: u"%1X"_q.arg(rounded / 10); : u"%1X"_q.arg(rounded / 10);
paintBadge(p, text, RectPart::TopLeft, color); paintBadge(p, text, RectPart::TopLeft, color);
} }
const auto text = (!_quality) const auto height = _quality.height;
const auto text = !height
? QString() ? QString()
: (_quality > 2000) : (height > 2000)
? u"4K"_q ? u"4K"_q
: (_quality > 1000) : (height > 1000)
? u"FHD"_q ? u"FHD"_q
: (_quality > 700) : (height > 700)
? u"HD"_q ? u"HD"_q
: u"SD"_q; : u"SD"_q;
if (!text.isEmpty()) { if (!text.isEmpty()) {

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais