Compare commits

...

20 Commits

Author SHA1 Message Date
John Preston
fd4a543bab Beta version 3.1.5: Fix theme change UI on Retina screens. 2021-09-28 22:27:41 +04:00
John Preston
d525e56053 Beta version 3.1.5: Fix build on Linux. 2021-09-28 22:08:28 +04:00
John Preston
dab5d1f994 Beta version 3.1.5.
- Choose one of 8 new preset themes for any individual private chat.
- Click on '...' menu > 'Change Colors' to pick a theme.
- Both chat participants will see the same theme
in that chat – on all their devices.
- Each new theme features colorful gradient message bubbles,
beautifully animated backgrounds and unique background patterns.
- All chat themes have day and night versions
and will follow your overall dark mode settings.
- Implement main window rounded corners on Windows 11.
- Fix audio capture from AirPods on macOS.
2021-09-28 22:00:51 +04:00
23rd
de3b52425c Removed unused HistoryInner::setFirstLoading. 2021-09-28 21:14:33 +04:00
John Preston
844fd58a97 Support Windows 11 rounded corners and themeable title bar. 2021-09-28 21:11:35 +04:00
John Preston
de2bad51d3 Scroll to currently selected theme. 2021-09-28 19:27:41 +04:00
John Preston
1424ea3540 Allow scrolling themes list. 2021-09-28 19:27:41 +04:00
John Preston
a8efd0ef3d Show chosen element in custom theme selector. 2021-09-28 19:27:41 +04:00
John Preston
1204e282d3 Fix attach icon in theme preview. 2021-09-28 19:27:41 +04:00
John Preston
6588242793 Prepare correct custom chat theme preview. 2021-09-28 19:27:41 +04:00
John Preston
b1ba9a42c6 Use Ui::GenerateBackgroundImage for preview in Settings. 2021-09-28 19:27:41 +04:00
John Preston
ab0d2bf9c6 Initial chat theme changing. 2021-09-28 19:27:41 +04:00
John Preston
80028e41f3 Bump OpenAL version in prepare script. 2021-09-28 19:25:39 +04:00
John Preston
2c581adc55 Add some hardening compiler / linker flags to dependencies. 2021-09-28 18:44:52 +04:00
John Preston
f0e8c1e325 Update lib_webview and docker patches revision. 2021-09-28 12:23:54 +04:00
John Preston
a2db9de4d7 Remove debug code. 2021-09-28 11:30:18 +04:00
John Preston
a228c62286 Fix "Nobody Viewed / Watched / Listened" seen state. 2021-09-27 18:51:50 +04:00
John Preston
37d940eca6 Beta version 3.1.4.
- Fix crash in network availability init.
- Fix assertion violation after a NaN-resulting std::round call.
2021-09-27 13:29:11 +04:00
John Preston
f7c24c54a1 Fix crash in failed network availability init. 2021-09-27 13:25:04 +04:00
John Preston
19ce1edc16 Use base::SafeRound instead of std::round.
Previous assertion violations because of NaN from std::round were
in video streaming, see commits 27d58ba07b, 8f5830d520.

Now the crashes happened in the ConvertScale() call from a background
thread when preparing an image from clipboard for sending to a chat.
2021-09-27 12:13:57 +04:00
90 changed files with 1269 additions and 373 deletions

View File

@@ -1040,6 +1040,8 @@ PRIVATE
ui/chat/attach/attach_item_single_file_preview.h
ui/chat/attach/attach_item_single_media_preview.cpp
ui/chat/attach/attach_item_single_media_preview.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
ui/effects/fireworks_animation.cpp
ui/effects/fireworks_animation.h
ui/effects/round_checkbox.cpp
@@ -1368,6 +1370,7 @@ if (WIN32)
/DELAYLOAD:gdiplus.dll
/DELAYLOAD:version.dll
/DELAYLOAD:dwmapi.dll
/DELAYLOAD:uxtheme.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
/DELAYLOAD:imm32.dll

View File

@@ -2871,6 +2871,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
"lng_filters_remove_yes" = "Remove";
"lng_chat_theme_change" = "Change colors";
"lng_chat_theme_none" = "No\nTheme";
"lng_chat_theme_apply" = "Apply Theme";
"lng_chat_theme_reset" = "Reset Theme";
"lng_chat_theme_dont" = "Do Not Set Theme";
"lng_chat_theme_title" = "Select theme";
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
"lng_photo_editor_menu_delete" = "Delete";
"lng_photo_editor_menu_flip" = "Flip";
"lng_photo_editor_menu_duplicate" = "Duplicate";

View File

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

View File

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

View File

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

View File

@@ -355,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
} else if (UpdateUserpics(state, item, peers)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.empty()) {
pushNext();
}
}, lifetime);

View File

@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
const auto frames = framesForStyle(st);
auto &image = frames->image;
const auto count = int(frames->ready.size());
const auto index = int(std::round(toggled * (count - 1)));
const auto index = int(base::SafeRound(toggled * (count - 1)));
Assert(index >= 0 && index < count);
if (!frames->ready[index]) {
frames->ready[index] = true;

View File

@@ -268,7 +268,8 @@ void Row::update(const InviteLinkData &data, TimeId now) {
void Row::updateExpireProgress(TimeId now) {
const auto updated = ComputeProgress(_data, now);
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
if (base::SafeRound(_progressTillExpire * 360)
!= base::SafeRound(updated * 360)) {
_progressTillExpire = updated;
const auto color = ComputeColor(_data, _progressTillExpire);
if (_color != color) {
@@ -291,7 +292,8 @@ crl::time Row::updateExpireIn() const {
if (_data.expireDate <= start) {
return 0;
}
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
return base::SafeRound(
(_data.expireDate - start) * crl::time(1000) / 720.);
}
QString Row::generateName() {

View File

@@ -209,7 +209,7 @@ void VideoBubble::updateSizeToFrame(QSize frame) {
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
} else {
const auto area = size.width() * size.height();
const auto w = int(std::round(std::max(
const auto w = int(base::SafeRound(std::max(
std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
1.)));
const auto h = area / w;

View File

@@ -57,7 +57,7 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
}
[[nodiscard]] QString StatusPercentString(float volume) {
return QString::number(int(std::round(volume * 200))) + '%';
return QString::number(int(base::SafeRound(volume * 200))) + '%';
}
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
@@ -492,7 +492,7 @@ int MembersRow::statusIconWidth(bool skipIcon) const {
const auto full = iconWidth
+ _statusIcon->percentWidth
+ st::normalFont->spacew;
return int(std::round(shown * full));
return int(base::SafeRound(shown * full));
}
int MembersRow::statusIconHeight() const {

View File

@@ -461,15 +461,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
const auto columns = slices;
const auto sizew = (outerWidth + skip) / float64(columns);
for (auto column = 0; column != columns; ++column) {
const auto left = int(std::round(column * sizew));
const auto width = int(std::round(column * sizew + sizew - skip))
- left;
const auto rows = int(std::round((count - index)
const auto left = int(base::SafeRound(column * sizew));
const auto width = int(
base::SafeRound(column * sizew + sizew - skip)) - left;
const auto rows = int(base::SafeRound((count - index)
/ float64(columns - column)));
const auto sizeh = (outerHeight + skip) / float64(rows);
for (auto row = 0; row != rows; ++row) {
const auto top = int(std::round(row * sizeh));
const auto height = int(std::round(
const auto top = int(base::SafeRound(row * sizeh));
const auto height = int(base::SafeRound(
row * sizeh + sizeh - skip)) - top;
auto &geometry = sizes[index];
geometry.columns = {
@@ -493,15 +493,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
const auto rows = slices;
const auto sizeh = (outerHeight + skip) / float64(rows);
for (auto row = 0; row != rows; ++row) {
const auto top = int(std::round(row * sizeh));
const auto height = int(std::round(row * sizeh + sizeh - skip))
- top;
const auto columns = int(std::round((count - index)
const auto top = int(base::SafeRound(row * sizeh));
const auto height = int(
base::SafeRound(row * sizeh + sizeh - skip)) - top;
const auto columns = int(base::SafeRound((count - index)
/ float64(rows - row)));
const auto sizew = (outerWidth + skip) / float64(columns);
for (auto column = 0; column != columns; ++column) {
const auto left = int(std::round(column * sizew));
const auto width = int(std::round(
const auto left = int(base::SafeRound(column * sizew));
const auto width = int(base::SafeRound(
column * sizew + sizew - skip)) - left;
auto &geometry = sizes[index];
geometry.rows = {

View File

@@ -242,7 +242,7 @@ vec4 background() {
QSize outer,
float factor) {
factor *= kBlurTextureSizeFactor;
const auto area = outer / int(std::round(factor * cScale() / 100));
const auto area = outer / int(base::SafeRound(factor * cScale() / 100));
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
return (scaled.width() > unscaled.width()
|| scaled.height() > unscaled.height())

View File

@@ -92,7 +92,7 @@ MenuVolumeItem::MenuVolumeItem(
const auto volume = _localMuted
? 0
: std::round(_slider->value() * kMaxVolumePercent);
: base::SafeRound(_slider->value() * kMaxVolumePercent);
const auto muteProgress =
_crossLineAnimation.value((!volume) ? 1. : 0.);
@@ -140,7 +140,7 @@ MenuVolumeItem::MenuVolumeItem(
};
_slider->setChangeFinishedCallback([=](float64 value) {
const auto newVolume = std::round(value * _maxVolume);
const auto newVolume = base::SafeRound(value * _maxVolume);
const auto muted = (value == 0);
if (!_cloudMuted && muted) {
@@ -175,7 +175,7 @@ MenuVolumeItem::MenuVolumeItem(
}
if (_waitingForUpdateVolume) {
const auto localVolume =
std::round(_slider->value() * _maxVolume);
base::SafeRound(_slider->value() * _maxVolume);
if ((localVolume != newVolume)
&& (_cloudVolume == newVolume)) {
_changeVolumeRequests.fire(int(localVolume));

View File

@@ -186,7 +186,7 @@ void EmojiInteractions::startIncoming(
}
const auto now = crl::now();
for (const auto &single : bunch.interactions) {
const auto at = now + crl::time(std::round(single.time * 1000));
const auto at = now + crl::time(base::SafeRound(single.time * 1000));
if (!animations.empty() && animations.back().scheduledAt >= at) {
continue;
}

View File

@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
{ -1, 1 },
{ 1, 1 },
} };
const auto corrected = int(std::round(delta / sqrt(2.)));
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
for (const auto &shift : diagonal) {
for (auto i = 0; i != corrected; ++i) {
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);

View File

@@ -68,6 +68,25 @@ std::map<int, const char*> BetaLogs() {
"- Reconnect without timeout when network availability changes.\n"
"- Crash fixes."
},
{
3001005,
"- Choose one of 8 new preset themes for any individual private chat.\n"
"- Click on '...' menu > 'Change Colors' to pick a theme.\n"
"- Both chat participants will see the same theme in that chat "
" on all their devices.\n"
"- Each new theme features colorful gradient message bubbles, "
"beautifully animated backgrounds and unique background patterns.\n"
"- All chat themes have day and night versions and will follow "
"your overall dark mode settings.\n"
"- Implement main window rounded corners on Windows 11.\n"
"- Fix audio capture from AirPods on macOS.\n"
}
};
};

View File

@@ -617,7 +617,7 @@ public:
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
return int(std::round(std::clamp(speed, 0.5, 2.0) * 100));
return int(base::SafeRound(std::clamp(speed, 0.5, 2.0) * 100));
}
[[nodiscard]] static float64 DeserializePlaybackSpeed(qint32 speed) {
if (speed < 10) {

View File

@@ -586,7 +586,7 @@ bool ParseCommonMap(
}
return string.toULongLong();
} else if ((*version).isDouble()) {
return uint64(std::round((*version).toDouble()));
return uint64(base::SafeRound((*version).toDouble()));
}
return 0ULL;
}();

View File

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

View File

@@ -387,26 +387,29 @@ rpl::producer<> CloudThemes::chatThemesUpdated() const {
}
std::optional<ChatTheme> CloudThemes::themeForEmoji(
const QString &emoji) const {
if (emoji.isEmpty()) {
const QString &emoticon) const {
const auto emoji = Ui::Emoji::Find(emoticon);
if (!emoji) {
return {};
}
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
return Ui::Emoji::Find(v.emoticon);
});
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
}
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
const QString &emoji) {
const QString &emoticon) {
const auto testing = TestingColors();
if (emoji.isEmpty()) {
if (!Ui::Emoji::Find(emoticon)) {
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
} else if (auto result = themeForEmoji(emoji)) {
} else if (auto result = themeForEmoji(emoticon)) {
if (testing) {
return rpl::single(
std::move(result)
) | rpl::then(chatThemesUpdated(
) | rpl::map([=] {
return themeForEmoji(emoji);
return themeForEmoji(emoticon);
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
return theme.has_value();
}));
@@ -419,7 +422,7 @@ rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
std::nullopt
) | rpl::then(chatThemesUpdated(
) | rpl::map([=] {
return themeForEmoji(emoji);
return themeForEmoji(emoticon);
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
return theme.has_value();
}) | rpl::take(limit));
@@ -482,12 +485,15 @@ QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
}
std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
const QString &emoji,
const QString &emoticon,
const QMap<QString, QString> &params) {
if (!TestingColors()) {
const auto emoji = Ui::Emoji::Find(emoticon);
if (!TestingColors() || !emoji) {
return std::nullopt;
}
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
return Ui::Emoji::Find(v.emoticon);
});
if (i == end(_chatThemes)) {
return std::nullopt;
}
@@ -552,7 +558,7 @@ void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
for (const auto &theme : list) {
theme.match([&](const MTPDchatTheme &data) {
_chatThemes.push_back({
.emoji = qs(data.vemoticon()),
.emoticon = qs(data.vemoticon()),
.light = CloudTheme::Parse(_session, data.vtheme(), true),
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
});

View File

@@ -50,7 +50,7 @@ struct CloudTheme {
};
struct ChatTheme {
QString emoji;
QString emoticon;
CloudTheme light;
CloudTheme dark;
};
@@ -71,15 +71,15 @@ public:
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
const QString &emoji) const;
const QString &emoticon) const;
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
const QString &emoji);
const QString &emoticon);
[[nodiscard]] static bool TestingColors();
static void SetTestingColors(bool testing);
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
const QString &emoji,
const QString &emoticon,
const QMap<QString, QString> &params);
void applyUpdate(const MTPTheme &theme);

View File

@@ -1004,19 +1004,24 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
return 0;
}
void PeerData::setThemeEmoji(const QString &emoji) {
if (_themeEmoji == emoji) {
void PeerData::setThemeEmoji(const QString &emoticon) {
if (_themeEmoticon == emoticon) {
return;
}
_themeEmoji = emoji;
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
_themeEmoticon = emoticon;
return;
}
_themeEmoticon = emoticon;
if (!emoticon.isEmpty()
&& !owner().cloudThemes().themeForEmoji(emoticon)) {
owner().cloudThemes().refreshChatThemes();
}
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
}
const QString &PeerData::themeEmoji() const {
return _themeEmoji;
return _themeEmoticon;
}
void PeerData::setIsBlocked(bool is) {

View File

@@ -459,7 +459,7 @@ public:
[[nodiscard]] Data::GroupCall *groupCall() const;
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
void setThemeEmoji(const QString &emoji);
void setThemeEmoji(const QString &emoticon);
[[nodiscard]] const QString &themeEmoji() const;
const PeerId id;
@@ -506,7 +506,7 @@ private:
LoadedStatus _loadedStatus = LoadedStatus::Not;
QString _about;
QString _themeEmoji;
QString _themeEmoticon;
};

View File

@@ -80,8 +80,9 @@ Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location) {
| (uint32(location.height) & 0xFFFFU);
return Storage::Cache::Key{
Data::kGeoPointCacheTag | (uint64(zoomscale) << 32) | widthheight,
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(std::round(std::abs(location.lon + 360.) * 1000000))
(uint64(base::SafeRound(
std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(base::SafeRound(std::abs(location.lon + 360.) * 1000000))
};
}

View File

@@ -150,7 +150,7 @@ void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
const auto angle = Normalized((isLeft ? 180 : 0)
+ (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
setRotation(shift
? (std::round(angle / kSnapAngle) * kSnapAngle) // Snap rotation.
? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
: angle);
} else {
QGraphicsItem::mouseMoveEvent(event);

View File

@@ -614,8 +614,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty());
bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty;
if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
bool noHistoryDisplayed = historyDisplayedEmpty;
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
const auto st = context.st;
const auto stm = &st->messageStyle(false, false);
if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) {
@@ -2328,11 +2328,6 @@ bool HistoryInner::wasSelectedText() const {
return _wasSelectedText;
}
void HistoryInner::setFirstLoading(bool loading) {
_firstLoading = loading;
update();
}
void HistoryInner::visibleAreaUpdated(int top, int bottom) {
auto scrolledUp = (top < _visibleAreaTop);
_visibleAreaTop = top;

View File

@@ -121,7 +121,6 @@ public:
void updateBotInfo(bool recount = true);
bool wasSelectedText() const;
void setFirstLoading(bool loading);
// updates history->scrollTopItem/scrollTopOffset
void visibleAreaUpdated(int top, int bottom);
@@ -378,8 +377,6 @@ private:
mutable int _curBlock = 0;
mutable int _curItem = 0;
bool _firstLoading = false;
style::cursor _cursor = style::cur_default;
SelectedItems _selected;
std::optional<Ui::ReportReason> _chooseForReportReason;

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/special_buttons.h"
#include "ui/emoji_config.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/choose_theme_controller.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/widgets/dropdown_menu.h"
@@ -1282,11 +1283,39 @@ void HistoryWidget::insertHashtagOrBotCommand(
}
}
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
return (isChoosingTheme() || _editMsgId)
? InlineBotQuery()
: ParseInlineBotQuery(&session(), _field);
}
AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
const auto result = (isChoosingTheme()
|| (_inlineBot && !_inlineLookingUpBot))
? AutocompleteQuery()
: ParseMentionHashtagBotCommandQuery(_field);
if (result.query.isEmpty()) {
return result;
} else if (result.query[0] == '#'
&& cRecentWriteHashtags().isEmpty()
&& cRecentSearchHashtags().isEmpty()) {
session().local().readRecentHashtagsAndBots();
} else if (result.query[0] == '@'
&& cRecentInlineBots().isEmpty()) {
session().local().readRecentHashtagsAndBots();
} else if (result.query[0] == '/'
&& ((_peer->isUser() && !_peer->asUser()->isBot()) || _editMsgId)) {
return AutocompleteQuery();
}
return result;
}
void HistoryWidget::updateInlineBotQuery() {
if (!_history) {
return;
}
const auto query = ParseInlineBotQuery(&session(), _field);
const auto query = parseInlineBotQuery();
if (_inlineBotUsername != query.username) {
_inlineBotUsername = query.username;
if (_inlineBotResolveRequestId) {
@@ -1369,10 +1398,11 @@ void HistoryWidget::orderWidgets() {
if (_groupCallBar) {
_groupCallBar->raise();
}
_topShadow->raise();
if (_fieldAutocomplete) {
_fieldAutocomplete->raise();
if (_chooseTheme) {
_chooseTheme->raise();
}
_topShadow->raise();
_fieldAutocomplete->raise();
if (_membersDropdown) {
_membersDropdown->raise();
}
@@ -1410,6 +1440,37 @@ bool HistoryWidget::updateStickersByEmoji() {
return (emoji != nullptr);
}
void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
const auto update = [=] {
updateInlineBotQuery();
updateControlsGeometry();
updateControlsVisibility();
};
if (peer.get() != _peer) {
return;
} else if (_chooseTheme) {
if (isChoosingTheme()) {
const auto was = base::take(_chooseTheme);
if (Ui::InFocusChain(this)) {
setInnerFocus();
}
update();
}
return;
} else if (_voiceRecordBar->isActive()) {
Ui::ShowMultilineToast({
.text = { tr::lng_chat_theme_cant_voice(tr::now) },
});
return;
}
_chooseTheme = std::make_unique<Ui::ChooseThemeController>(
this,
controller(),
peer);
_chooseTheme->shouldBeShownValue(
) | rpl::start_with_next(update, _chooseTheme->lifetime());
}
void HistoryWidget::fieldChanged() {
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
@@ -1556,7 +1617,9 @@ void HistoryWidget::setInnerFocus() {
if (_scroll->isHidden()) {
setFocus();
} else if (_list) {
if (_nonEmptySelection
if (_chooseTheme && _chooseTheme->shouldBeShown()) {
_chooseTheme->setFocus();
} else if (_nonEmptySelection
|| (_list && _list->wasSelectedText())
|| isRecording()
|| isBotStart()
@@ -1925,6 +1988,7 @@ void HistoryWidget::showHistory(
_pinnedTracker = nullptr;
_groupCallBar = nullptr;
_groupCallTracker = nullptr;
_chooseTheme = nullptr;
_membersDropdown.destroy();
_scrollToAnimation.stop();
@@ -2322,52 +2386,42 @@ void HistoryWidget::updateControlsVisibility() {
if (_contactStatus) {
_contactStatus->show();
}
if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart() || isReportMessages())) {
if (isReportMessages()) {
_unblock->hide();
_joinChannel->hide();
_muteUnmute->hide();
_botStart->hide();
if (_reportMessages->isHidden()) {
_reportMessages->clearState();
_reportMessages->show();
}
if (isChoosingTheme()
|| (!editingMessage()
&& (isBlocked()
|| isJoinChannel()
|| isMuteUnmute()
|| isBotStart()
|| isReportMessages()))) {
const auto toggle = [&](Ui::FlatButton *shown) {
const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
if (button.get() != shown) {
button->hide();
} else if (button->isHidden()) {
button->clearState();
button->show();
}
};
toggleOne(_reportMessages);
toggleOne(_joinChannel);
toggleOne(_muteUnmute);
toggleOne(_botStart);
toggleOne(_unblock);
};
if (isChoosingTheme()) {
_chooseTheme->show();
setInnerFocus();
toggle(nullptr);
} else if (isReportMessages()) {
toggle(_reportMessages);
} else if (isBlocked()) {
_reportMessages->hide();
_joinChannel->hide();
_muteUnmute->hide();
_botStart->hide();
if (_unblock->isHidden()) {
_unblock->clearState();
_unblock->show();
}
toggle(_unblock);
} else if (isJoinChannel()) {
_reportMessages->hide();
_unblock->hide();
_muteUnmute->hide();
_botStart->hide();
if (_joinChannel->isHidden()) {
_joinChannel->clearState();
_joinChannel->show();
}
toggle(_joinChannel);
} else if (isMuteUnmute()) {
_reportMessages->hide();
_unblock->hide();
_joinChannel->hide();
_botStart->hide();
if (_muteUnmute->isHidden()) {
_muteUnmute->clearState();
_muteUnmute->show();
}
toggle(_muteUnmute);
} else if (isBotStart()) {
_reportMessages->hide();
_unblock->hide();
_joinChannel->hide();
_muteUnmute->hide();
if (_botStart->isHidden()) {
_botStart->clearState();
_botStart->show();
}
toggle(_botStart);
}
_kbShown = false;
_fieldAutocomplete->hide();
@@ -3289,6 +3343,9 @@ void HistoryWidget::hideChildWidgets() {
if (_voiceRecordBar) {
_voiceRecordBar->hideFast();
}
if (_chooseTheme) {
_chooseTheme->hide();
}
hideChildren();
}
@@ -3908,7 +3965,7 @@ void HistoryWidget::inlineBotResolveDone(
}();
session().data().processChats(data.vchats());
const auto query = ParseInlineBotQuery(&session(), _field);
const auto query = parseInlineBotQuery();
if (_inlineBotUsername == query.username) {
applyInlineBotQuery(
query.lookingUpBot ? resolvedBot : query.bot,
@@ -3953,6 +4010,10 @@ bool HistoryWidget::isJoinChannel() const {
return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
}
bool HistoryWidget::isChoosingTheme() const {
return _chooseTheme && _chooseTheme->shouldBeShown();
}
bool HistoryWidget::isMuteUnmute() const {
return _peer
&& ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
@@ -4337,24 +4398,7 @@ void HistoryWidget::checkFieldAutocomplete() {
return;
}
const auto isInlineBot = _inlineBot && !_inlineLookingUpBot;
const auto autocomplete = isInlineBot
? AutocompleteQuery()
: ParseMentionHashtagBotCommandQuery(_field);
if (!autocomplete.query.isEmpty()) {
if (autocomplete.query[0] == '#'
&& cRecentWriteHashtags().isEmpty()
&& cRecentSearchHashtags().isEmpty()) {
session().local().readRecentHashtagsAndBots();
} else if (autocomplete.query[0] == '@'
&& cRecentInlineBots().isEmpty()) {
session().local().readRecentHashtagsAndBots();
} else if (autocomplete.query[0] == '/'
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|| _editMsgId)) {
return;
}
}
const auto autocomplete = parseMentionHashtagBotCommandQuery();
_fieldAutocomplete->showFiltered(
_peer,
autocomplete.query,
@@ -4922,7 +4966,14 @@ void HistoryWidget::updateHistoryGeometry(
if (_contactStatus) {
newScrollHeight -= _contactStatus->height();
}
if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute() || isReportMessages())) {
if (isChoosingTheme()) {
newScrollHeight -= _chooseTheme->height();
} else if (!editingMessage()
&& (isBlocked()
|| isBotStart()
|| isJoinChannel()
|| isMuteUnmute()
|| isReportMessages())) {
newScrollHeight -= _unblock->height();
} else {
if (editingMessage() || _canSendMessages) {
@@ -6136,16 +6187,17 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
}
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
if (_voiceRecordBar->isActive()) {
controller()->show(
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
return;
}
if (const auto media = item->media()) {
if (media->allowsEditCaption()) {
controller()->show(Box<EditCaptionBox>(controller(), item));
return;
}
} else if (_chooseTheme) {
toggleChooseChatTheme(_peer);
} else if (_voiceRecordBar->isActive()) {
controller()->show(
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
return;
}
if (isRecording()) {

View File

@@ -25,6 +25,8 @@ struct FileLoadResult;
struct SendingAlbum;
enum class SendMediaType;
class MessageLinksParser;
struct InlineBotQuery;
struct AutocompleteQuery;
namespace MTP {
class Error;
@@ -77,6 +79,7 @@ enum class ReportReason;
namespace Toast {
class Instance;
} // namespace Toast
class ChooseThemeController;
} // namespace Ui
namespace Window {
@@ -237,6 +240,8 @@ public:
void clearDelayedShowAt();
void saveFieldToHistoryLocalDraft();
void toggleChooseChatTheme(not_null<PeerData*> peer);
void applyCloudDraft(History *history);
void updateHistoryDownPosition();
@@ -454,6 +459,10 @@ private:
std::optional<QString> writeRestriction() const;
void orderWidgets();
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
[[nodiscard]] auto parseMentionHashtagBotCommandQuery() const
-> AutocompleteQuery;
void clearInlineBot();
void inlineBotChanged();
@@ -585,19 +594,21 @@ private:
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
void inlineBotResolveFail(const MTP::Error &error, const QString &username);
bool isRecording() const;
[[nodiscard]] bool isRecording() const;
bool isBotStart() const;
bool isBlocked() const;
bool isJoinChannel() const;
bool isMuteUnmute() const;
bool isReportMessages() const;
[[nodiscard]] bool isBotStart() const;
[[nodiscard]] bool isBlocked() const;
[[nodiscard]] bool isJoinChannel() const;
[[nodiscard]] bool isMuteUnmute() const;
[[nodiscard]] bool isReportMessages() const;
bool updateCmdStartShown();
void updateSendButtonType();
bool showRecordButton() const;
bool showInlineBotCancel() const;
[[nodiscard]] bool showRecordButton() const;
[[nodiscard]] bool showInlineBotCancel() const;
void refreshSilentToggle();
[[nodiscard]] bool isChoosingTheme() const;
void setupScheduledToggle();
void refreshScheduledToggle();
@@ -689,7 +700,7 @@ private:
bool _unreadMentionsIsShown = false;
object_ptr<Ui::HistoryDownButton> _unreadMentions;
object_ptr<FieldAutocomplete> _fieldAutocomplete;
const object_ptr<FieldAutocomplete> _fieldAutocomplete;
object_ptr<Support::Autocomplete> _supportAutocomplete;
std::unique_ptr<MessageLinksParser> _fieldLinksParser;
@@ -726,6 +737,8 @@ private:
object_ptr<Ui::ScrollArea> _kbScroll;
const not_null<BotKeyboard*> _keyboard;
std::unique_ptr<Ui::ChooseThemeController> _chooseTheme;
object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr };
base::Timer _membersDropdownShowTimer;

View File

@@ -130,7 +130,7 @@ void PaintWaveform(
const auto samplesCount = wf
? wf->size()
: ::Media::Player::kWaveformSamplesCount;
const auto activeWidth = std::round(availableWidth * progress);
const auto activeWidth = base::SafeRound(availableWidth * progress);
const auto &barWidth = st::historyRecordWaveformBar;
const auto barFullWidth = barWidth + st::msgWaveformSkip;
@@ -774,7 +774,7 @@ void RecordLock::drawProgress(Painter &p) {
_lockToStopProgress);
const auto blockRectTop = anim::interpolateF(
size.height() - blockHeight,
std::round((size.height() - blockRectHeight) / 2.),
base::SafeRound((size.height() - blockRectHeight) / 2.),
_lockToStopProgress);
const auto blockRect = QRectF(

View File

@@ -477,7 +477,8 @@ void ListWidget::scrollToAnimationCallback(
int relativeTo) {
if (!attachToId) {
// Animated scroll to bottom.
const auto current = int(std::round(_scrollToAnimation.value(0)));
const auto current = int(base::SafeRound(
_scrollToAnimation.value(0)));
_delegate->listScrollTo(height()
- (_visibleBottom - _visibleTop)
+ current);
@@ -488,7 +489,7 @@ void ListWidget::scrollToAnimationCallback(
if (!attachToView) {
_scrollToAnimation.stop();
} else {
const auto current = int(std::round(_scrollToAnimation.value(
const auto current = int(base::SafeRound(_scrollToAnimation.value(
relativeTo)));
_delegate->listScrollTo(itemTop(attachToView) + current);
}

View File

@@ -308,7 +308,7 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
// We have to use QFontMetricsF instead of
// FontData::spacew for more precise calculation.
const auto mf = QFontMetricsF(_st.font->f);
_spacesCount = std::round(
_spacesCount = base::SafeRound(
_sendActionAnimation.widthNoMargins()
/ mf.horizontalAdvance(' '));
}

View File

@@ -91,7 +91,7 @@ void PaintWaveform(
const auto wfSize = wf
? wf->size()
: ::Media::Player::kWaveformSamplesCount;
const auto activeWidth = std::round(availableWidth * progress);
const auto activeWidth = base::SafeRound(availableWidth * progress);
const auto &barWidth = st::msgWaveformBar;
const auto barCount = std::min(
@@ -526,7 +526,7 @@ void Document::draw(
}();
if (voice->seeking()) {
voiceStatusOverride = Ui::FormatPlayedText(
std::round(progress * voice->_lastDurationMs) / 1000,
base::SafeRound(progress * voice->_lastDurationMs) / 1000,
voice->_lastDurationMs / 1000);
}

View File

@@ -182,7 +182,7 @@ QSize GroupedMedia::countCurrentSize(int newWidth) {
const auto initialSpacing = st::historyGroupSkip;
const auto factor = newWidth / float64(maxWidth());
const auto scale = [&](int value) {
return int(std::round(value * factor));
return int(base::SafeRound(value * factor));
};
const auto spacing = scale(initialSpacing);
for (auto &part : _parts) {

View File

@@ -963,7 +963,7 @@ void Poll::paintCloseByTimer(
const auto part = std::max(
left / float64(radial),
1. / FullArcLength);
const auto length = int(std::round(FullArcLength * part));
const auto length = int(base::SafeRound(FullArcLength * part));
auto pen = regular->p;
pen.setWidth(st::historyPollRadio.thickness);
pen.setCapStyle(Qt::RoundCap);
@@ -1050,7 +1050,7 @@ int Poll::paintAnswer(
}
if (opacity > 0.) {
const auto percent = QString::number(
int(std::round(animation->percent.current()))) + '%';
int(base::SafeRound(animation->percent.current()))) + '%';
const auto percentWidth = st::historyPollPercentFont->width(
percent);
p.setOpacity(opacity);

View File

@@ -1353,6 +1353,10 @@ void MainWidget::clearChooseReportMessages() {
_history->setChooseReportMessagesDetails({}, nullptr);
}
void MainWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
_history->toggleChooseChatTheme(peer);
}
void MainWidget::ui_showPeerHistory(
PeerId peerId,
const SectionShow &params,

View File

@@ -213,6 +213,8 @@ public:
Fn<void(MessageIdsList)> done);
void clearChooseReportMessages();
void toggleChooseChatTheme(not_null<PeerData*> peer);
void ui_showPeerHistory(
PeerId peer,
const SectionShow &params,

View File

@@ -49,7 +49,7 @@ int CoarseTuneForSpeed(float64 speed) {
constexpr auto kTuneSteps = 12;
const auto tuneRatio = std::log(speed) / std::log(2.);
return -int(std::round(kTuneSteps * tuneRatio));
return -int(base::SafeRound(kTuneSteps * tuneRatio));
}
} // namespace

View File

@@ -654,7 +654,7 @@ void Instance::finishSeeking(AudioMsgId::Type type, float64 progress) {
const auto &info = streamed->instance.info();
const auto duration = info.audio.state.duration;
if (duration != kTimeUnknown) {
const auto position = crl::time(std::round(
const auto position = crl::time(base::SafeRound(
std::clamp(progress, 0., 1.) * duration));
streamed->instance.play(streamingOptions(
streamed->id,

View File

@@ -12,8 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/concurrent_timer.h"
#include "core/crash_reports.h"
#include <cfenv>
namespace Media {
namespace Streaming {
namespace {
@@ -71,31 +69,6 @@ static_assert(kDisplaySkipped != kTimeUnknown);
return result;
}
[[nodiscard]] float64 SafeRound(float64 value) {
Expects(!std::isnan(value));
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
const auto errors = std::fetestexcept(FE_ALL_EXCEPT);
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
LOG(("Streaming Error: Got second NAN in std::round(%1), fe: %2."
).arg(value
).arg(errors));
std::feclearexcept(FE_ALL_EXCEPT);
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
CrashReports::SetAnnotation("FE-Error-Value", QString::number(value));
CrashReports::SetAnnotation("FE-Errors-Were", QString::number(errors));
CrashReports::SetAnnotation(
"FE-Errors-Now",
QString::number(std::fetestexcept(FE_ALL_EXCEPT)));
Unexpected("NAN after third std::round.");
}
} // namespace
class VideoTrackObject final {
@@ -713,7 +686,7 @@ TimePoint VideoTrackObject::trackTime() const {
}
const auto adjust = (result.worldTime - _syncTimePoint.worldTime);
const auto adjustSpeed = adjust * _options.speed;
const auto roundAdjustSpeed = SafeRound(adjustSpeed);
const auto roundAdjustSpeed = base::SafeRound(adjustSpeed);
const auto timeRoundAdjustSpeed = crl::time(roundAdjustSpeed);
result.trackTime = _syncTimePoint.trackTime + timeRoundAdjustSpeed;
return result;
@@ -848,7 +821,7 @@ auto VideoTrack::Shared::presentFrame(
}
const auto trackLeft = position - time.trackTime;
const auto adjustedBySpeed = trackLeft / playbackSpeed;
const auto roundedAdjustedBySpeed = SafeRound(adjustedBySpeed);
const auto roundedAdjustedBySpeed = base::SafeRound(adjustedBySpeed);
frame->display = time.worldTime
+ addedWorldTimeDelay
+ crl::time(roundedAdjustedBySpeed);

View File

@@ -32,7 +32,7 @@ namespace {
constexpr auto kThumbDuration = crl::time(150);
int Round(float64 value) {
return int(std::round(value));
return int(base::SafeRound(value));
}
using Context = GroupThumbs::Context;

View File

@@ -441,7 +441,7 @@ void OverlayWidget::RendererGL::paintControl(
Assert(meta.icon == &icon);
const auto &bg = st::mediaviewControlBg->c;
const auto bgAlpha = int(std::round(bg.alpha() * outerOpacity));
const auto bgAlpha = int(base::SafeRound(bg.alpha() * outerOpacity));
const auto offset = kControlsOffset + (meta.index * kControlValues) / 4;
const auto fgOffset = offset + 2;
const auto bgRect = transformRect(outer);

View File

@@ -1396,7 +1396,7 @@ void Pip::paintProgressBar(
float64 progress,
int radius,
float64 active) const {
const auto done = int(std::round(rect.width() * progress));
const auto done = int(base::SafeRound(rect.width() * progress));
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
if (done > 0) {

View File

@@ -485,9 +485,9 @@ void Pip::RendererGL::paintRadialLoading(
const auto fadeAlpha = controlsShown * fade.alphaF();
const auto fgAlpha = 1. - fadeAlpha;
const auto color = (fadeAlpha == 0.) ? fg : QColor(
int(std::round(fg.red() * fgAlpha + fade.red() * fadeAlpha)),
int(std::round(fg.green() * fgAlpha + fade.green() * fadeAlpha)),
int(std::round(fg.blue() * fgAlpha + fade.blue() * fadeAlpha)),
int(base::SafeRound(fg.red() * fgAlpha + fade.red() * fadeAlpha)),
int(base::SafeRound(fg.green() * fgAlpha + fade.green() * fadeAlpha)),
int(base::SafeRound(fg.blue() * fgAlpha + fade.blue() * fadeAlpha)),
fg.alpha());
_owner->paintRadialLoadingContent(p, newInner, color);

View File

@@ -102,7 +102,7 @@ MenuSpeedItem::MenuSpeedItem(
enableMouseSelecting(_slider.get());
_slider->setAlwaysDisplayMarker(true);
_slider->setValue((std::round(startSpeed * 100.) - kMinSpeed)
_slider->setValue((base::SafeRound(startSpeed * 100.) - kMinSpeed)
/ (kMaxSpeed - kMinSpeed));
for (const auto &sticked : kSpeedStickedValues) {
@@ -438,7 +438,7 @@ void PlaybackControls::setLoadingProgress(int ready, int total) {
_loadingPercent = -1;
}
const auto progress = total ? (ready / float64(total)) : 0.;
const auto percent = int(std::round(progress * 100));
const auto percent = int(base::SafeRound(progress * 100));
if (_loadingPercent != percent) {
_loadingPercent = percent;
_downloadProgress->setText(QString::number(percent) + '%');

View File

@@ -114,7 +114,7 @@ std::vector<DnsEntry> ParseDnsResponse(
const auto object = elem.toObject();
if (typeRestriction) {
const auto typeIt = object.find("type");
const auto type = int(std::round((*typeIt).toDouble()));
const auto type = int(base::SafeRound((*typeIt).toDouble()));
if (!(*typeIt).isDouble()) {
LOG(("Config Error: Not a number in type field "
"in Answer array in dns response JSON."));
@@ -136,7 +136,7 @@ std::vector<DnsEntry> ParseDnsResponse(
const auto ttlIt = object.find("TTL");
const auto ttl = (ttlIt != object.constEnd())
? crl::time(std::round((*ttlIt).toDouble()))
? crl::time(base::SafeRound((*ttlIt).toDouble()))
: crl::time(0);
result.push_back({ (*dataIt).toString(), ttl });
}

View File

@@ -544,7 +544,9 @@ MTPVector<MTPJSONObjectValue> SessionPrivate::prepareInitParams() {
sliced -= 24 * 3600;
}
const auto sign = (sliced < 0) ? -1 : 1;
const auto rounded = std::round(std::abs(sliced) / 900.) * 900 * sign;
const auto rounded = base::SafeRound(std::abs(sliced) / 900.)
* 900
* sign;
return MTP_vector<MTPJSONObjectValue>(
1,
MTP_jsonObjectValue(

View File

@@ -229,7 +229,7 @@ struct SimpleFieldState {
QString()
).toDouble();
return QString::number(
int64(std::round(real * std::pow(10., rule.exponent))));
int64(base::SafeRound(real * std::pow(10., rule.exponent))));
} else if (config.type == FieldType::CardNumber
|| config.type == FieldType::CardCVC) {
return QString(parsed).replace(

View File

@@ -452,8 +452,12 @@ void FormSummary::setupSuggestedTips(not_null<VerticalLayout*> layout) {
for (auto i = 0; i != count; ++i) {
const auto button = buttons[rowStart + i].widget;
auto right = x + buttonWidths[i];
button->setFullWidth(int(std::round(right) - std::round(x)));
button->moveToLeft(int(std::round(x)), height, outerWidth);
button->setFullWidth(
int(base::SafeRound(right) - base::SafeRound(x)));
button->moveToLeft(
int(base::SafeRound(x)),
height,
outerWidth);
x = right + skip;
}
height += buttons[0].widget->height() + skip;

View File

@@ -240,7 +240,8 @@ TimeId CalculateOnlineTill(not_null<PeerData*> peer) {
: indexOf(peer);
const auto &entry = _pins[index];
entry->shift = entry->deltaShift
+ std::round(entry->shiftAnimation.value(entry->finalShift));
+ base::SafeRound(
entry->shiftAnimation.value(entry->finalShift));
if (entry->deltaShift && !entry->shiftAnimation.animating()) {
entry->finalShift += entry->deltaShift;
entry->deltaShift = 0;

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "base/crc32hash.h"
#include "base/platform/win/base_windows_wrl.h"
#include "base/platform/base_platform_info.h"
#include "core/application.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
@@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <Shobjidl.h>
#include <shellapi.h>
#include <WtsApi32.h>
#include <dwmapi.h>
#include <windows.ui.viewmanagement.h>
#include <UIViewSettingsInterop.h>
@@ -401,11 +403,9 @@ void MainWindow::initHook() {
}
void MainWindow::validateWindowTheme(bool native, bool night) {
if (!Dlls::SetWindowTheme) {
return;
} else if (!IsWindows8OrGreater()) {
if (!IsWindows8OrGreater()) {
const auto empty = native ? nullptr : L" ";
Dlls::SetWindowTheme(ps_hWnd, empty, empty);
SetWindowTheme(ps_hWnd, empty, empty);
QApplication::setStyle(QStyleFactory::create(u"Windows"_q));
#if 0
} else if (!Platform::IsDarkModeSupported()/*
@@ -416,7 +416,7 @@ void MainWindow::validateWindowTheme(bool native, bool night) {
return;
#endif
} else if (!native) {
Dlls::SetWindowTheme(ps_hWnd, nullptr, nullptr);
SetWindowTheme(ps_hWnd, nullptr, nullptr);
return;
}
@@ -435,13 +435,13 @@ void MainWindow::validateWindowTheme(bool native, bool night) {
sizeof(darkValue)
};
Dlls::SetWindowCompositionAttribute(ps_hWnd, &data);
} else if (kSystemVersion.microVersion() >= 17763 && Dlls::DwmSetWindowAttribute) {
static const auto DWMWA_USE_IMMERSIVE_DARK_MODE = (kSystemVersion.microVersion() >= 18985)
} else if (kSystemVersion.microVersion() >= 17763) {
static const auto kDWMWA_USE_IMMERSIVE_DARK_MODE = (kSystemVersion.microVersion() >= 18985)
? DWORD(20)
: DWORD(19);
Dlls::DwmSetWindowAttribute(
DwmSetWindowAttribute(
ps_hWnd,
DWMWA_USE_IMMERSIVE_DARK_MODE,
kDWMWA_USE_IMMERSIVE_DARK_MODE,
&darkValue,
sizeof(darkValue));
}
@@ -457,7 +457,7 @@ void MainWindow::validateWindowTheme(bool native, bool night) {
//const auto updateWindowTheme = [&] {
// const auto set = [&](LPCWSTR name) {
// return Dlls::SetWindowTheme(ps_hWnd, name, nullptr);
// return SetWindowTheme(ps_hWnd, name, nullptr);
// };
// if (!night || FAILED(set(L"DarkMode_Explorer"))) {
// set(L"Explorer");

View File

@@ -36,13 +36,12 @@ SafeIniter::SafeIniter() {
LOAD_SYMBOL(LibShell32, SHChangeNotify);
LOAD_SYMBOL(LibShell32, SetCurrentProcessExplicitAppUserModelID);
const auto LibUxTheme = LoadLibrary(L"uxtheme.dll");
LOAD_SYMBOL(LibUxTheme, SetWindowTheme);
//if (IsWindows10OrGreater()) {
// static const auto kSystemVersion = QOperatingSystemVersion::current();
// static const auto kMinor = kSystemVersion.minorVersion();
// static const auto kBuild = kSystemVersion.microVersion();
// if (kMinor > 0 || (kMinor == 0 && kBuild >= 17763)) {
// const auto LibUxTheme = LoadLibrary(L"uxtheme.dll");
// if (kBuild < 18362) {
// LOAD_SYMBOL(LibUxTheme, AllowDarkModeForApp, 135);
// } else {
@@ -62,9 +61,6 @@ SafeIniter::SafeIniter() {
LOAD_SYMBOL(LibPropSys, PropVariantToString);
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
const auto LibDwmApi = LoadLibrary(L"dwmapi.dll");
LOAD_SYMBOL(LibDwmApi, DwmSetWindowAttribute);
const auto LibPsApi = LoadLibrary(L"psapi.dll");
LOAD_SYMBOL(LibPsApi, GetProcessMemoryInfo);

View File

@@ -24,12 +24,6 @@ namespace Dlls {
void CheckLoadedModules();
// UXTHEME.DLL
inline HRESULT(__stdcall *SetWindowTheme)(
HWND hWnd,
LPCWSTR pszSubAppName,
LPCWSTR pszSubIdList);
//inline void(__stdcall *RefreshImmersiveColorPolicyState)();
//
//inline BOOL(__stdcall *AllowDarkModeForApp)(BOOL allow);
@@ -94,14 +88,6 @@ inline HRESULT(__stdcall *PSStringFromPropertyKey)(
_Out_writes_(cch) LPWSTR psz,
_In_ UINT cch);
// DWMAPI.DLL
inline HRESULT(__stdcall *DwmSetWindowAttribute)(
HWND hwnd,
DWORD dwAttribute,
_In_reads_bytes_(cbAttribute) LPCVOID pvAttribute,
DWORD cbAttribute);
// PSAPI.DLL
inline BOOL(__stdcall *GetProcessMemoryInfo)(

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/chat/attach/attach_extensions.h"
#include "ui/chat/chat_theme.h"
#include "ui/layers/generic_box.h"
#include "ui/effects/radial_animation.h"
#include "ui/style/style_palette_colorizer.h"
@@ -362,7 +363,7 @@ void ColorsPalette::updateInnerGeometry() {
const auto y = st::settingsSectionSkip * 2;
auto x = float64(padding.left());
for (const auto &button : _buttons) {
button->moveToLeft(int(std::round(x)), y);
button->moveToLeft(int(base::SafeRound(x)), y);
x += size + skip;
}
inner->resize(inner->width(), y + size);
@@ -556,61 +557,61 @@ void BackgroundRow::updateImage() {
const auto size = st::settingsBackgroundThumb;
const auto fullsize = size * cIntRetinaFactor();
// We use Format_RGB32 so that DestinationIn shows black, not transparent.
// Then we'll convert to Format_ARGB32_Premultiplied for round corners.
auto back = QImage(fullsize, fullsize, QImage::Format_RGB32);
back.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&back);
PainterHighQualityEnabler hq(p);
const auto background = Window::Theme::Background();
if (const auto color = background->colorForFill()) {
p.fillRect(0, 0, size, size, *color);
} else {
const auto gradient = background->gradientForFill();
const auto patternOpacity = background->paper().patternOpacity();
if (!gradient.isNull()) {
auto hq = PainterHighQualityEnabler(p);
p.drawImage(QRect(0, 0, size, size), gradient);
if (patternOpacity >= 0.) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
} else {
p.setCompositionMode(
QPainter::CompositionMode_DestinationIn);
}
const auto &background = *Window::Theme::Background();
const auto &paper = background.paper();
const auto &prepared = background.prepared();
const auto preparePattern = [&] {
const auto paintPattern = [&](QPainter &p, bool inverted) {
if (prepared.isNull()) {
return;
}
const auto &prepared = background->prepared();
if (!prepared.isNull()) {
const auto pattern = background->paper().isPattern();
const auto w = prepared.width();
const auto h = prepared.height();
const auto use = [&] {
if (!pattern) {
return std::min(w, h);
}
const auto scaledw = w * st::windowMinHeight / h;
const auto result = (w * size) / scaledw;
return std::min({ result, w, h });
}();
p.drawImage(
QRect(0, 0, size, size),
prepared,
QRect((w - use) / 2, (h - use) / 2, use, use));
}
if (!gradient.isNull()
&& !prepared.isNull()
&& patternOpacity < 0.
&& patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(1. + patternOpacity);
p.fillRect(QRect(0, 0, size, size), Qt::black);
const auto w = prepared.width();
const auto h = prepared.height();
const auto s = [&] {
const auto scaledw = w * st::windowMinHeight / h;
const auto result = (w * size) / scaledw;
return std::min({ result, w, h });
}();
auto small = prepared.copy((w - s) / 2, (h - s) / 2, s, s);
if (inverted) {
small = Ui::InvertPatternImage(std::move(small));
}
p.drawImage(QRect(0, 0, size, size), small);
};
return Ui::GenerateBackgroundImage(
{ fullsize, fullsize },
paper.backgroundColors(),
paper.gradientRotation(),
paper.patternOpacity(),
paintPattern);
};
const auto prepareNormal = [&] {
auto result = QImage(
QSize{ fullsize, fullsize },
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
if (const auto color = background.colorForFill()) {
result.fill(*color);
return result;
} else if (prepared.isNull()) {
result.fill(Qt::transparent);
return result;
}
}
back = std::move(back).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
Painter p(&result);
PainterHighQualityEnabler hq(p);
const auto w = prepared.width();
const auto h = prepared.height();
const auto s = std::min(w, h);
p.drawImage(
QRect(0, 0, size, size),
prepared,
QRect((w - s) / 2, (h - s) / 2, s, s));
p.end();
return result;
};
auto back = (paper.isPattern() || !background.gradientForFill().isNull())
? preparePattern()
: prepareNormal();
Images::prepareRound(back, ImageRoundRadius::Small);
_background = Ui::PixmapFromImage(std::move(back));
_background.setDevicePixelRatio(cRetinaFactor());
@@ -1159,7 +1160,7 @@ void SetupDefaultThemes(
auto left = padding.left() + 0.;
for (const auto button : buttons) {
button->resizeToWidth(single);
button->moveToLeft(int(std::round(left)), 0);
button->moveToLeft(int(base::SafeRound(left)), 0);
left += button->width() + skip;
}
}, block->lifetime());

View File

@@ -64,11 +64,11 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
const auto scaledWidth = [&] {
return (width > height)
? kThumbnailSize
: int(std::round(kThumbnailSize * width / float64(height)));
: int(base::SafeRound(kThumbnailSize * width / float64(height)));
};
const auto scaledHeight = [&] {
return (width > height)
? int(std::round(kThumbnailSize * height / float64(width)))
? int(base::SafeRound(kThumbnailSize * height / float64(width)))
: kThumbnailSize;
};
result.image = scaled

View File

@@ -85,7 +85,7 @@ QString StateDescription(const BlobState &state, tr::phrase<> activeText) {
return tr::lng_emoji_set_loading(
tr::now,
lt_percent,
QString::number(int(std::round(percent))) + '%',
QString::number(int(base::SafeRound(percent))) + '%',
lt_progress,
Ui::FormatDownloadText(data.already, data.size));
}, [](const Failed &data) {

View File

@@ -128,7 +128,7 @@ object_ptr<Ui::RpWidget> CreateSliderForTTL(
// Try to fill the line with exact number of dash segments.
// UPD Doesn't work so well because it changes when clicking.
//const auto length = till - from;
//const auto offSegmentsCount = int(std::round(
//const auto offSegmentsCount = int(base::SafeRound(
// (length - st->dashOn) / (st->dashOn + st->dashOff)));
//const auto onSegmentsCount = offSegmentsCount + 1;
//const auto idealLength = offSegmentsCount * st->dashOff

View File

@@ -263,7 +263,7 @@ void AlbumPreview::updateSize() {
} else if (!_sendWay.groupFiles()) {
return _photosHeight;
} else {
return int(std::round(_thumbsHeightAnimation.value(
return int(base::SafeRound(_thumbsHeightAnimation.value(
_thumbsHeight)));
}
}();

View File

@@ -270,7 +270,7 @@ void AlbumThumbnail::drawSimpleFrame(Painter &p, QRect to, QSize size) const {
const auto scaleWidth = to.width() / float64(width);
const auto scaleHeight = to.height() / float64(height);
const auto Round = [](float64 value) {
return int(std::round(value));
return int(base::SafeRound(value));
};
const auto [from, fillBlack] = [&] {
if (previewWidth < width && previewHeight < height) {
@@ -474,7 +474,7 @@ void AlbumThumbnail::suggestMove(float64 delta, Fn<void()> callback) {
}
QRect AlbumThumbnail::countRealGeometry() const {
const auto addLeft = int(std::round(
const auto addLeft = int(base::SafeRound(
_suggestedMoveAnimation.value(_suggestedMove) * _lastShrinkValue));
const auto current = _layout.geometry;
const auto realTopLeft = current.topLeft()

View File

@@ -124,9 +124,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
QImage::Format_ARGB32_Premultiplied),
.gradient = gradient,
.area = request.area,
.waitingForNegativePattern = (request.background.isPattern
&& request.background.prepared.isNull()
&& request.background.patternOpacity < 0.)
.waitingForNegativePattern
= request.background.waitingForNegativePattern()
};
} else {
const auto rects = ComputeChatBackgroundRects(
@@ -427,6 +426,8 @@ void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
_cacheBackgroundTimer->cancel();
}
cacheBackgroundNow();
} else {
_repaintBackgroundRequests.fire({});
}
}
@@ -511,6 +512,11 @@ const BackgroundState &ChatTheme::backgroundState(QSize area) {
return _backgroundState;
}
void ChatTheme::clearBackgroundState() {
_backgroundState = BackgroundState();
_backgroundFade.stop();
}
bool ChatTheme::readyForBackgroundRotation() const {
Expects(_cacheBackgroundTimer.has_value());

View File

@@ -32,6 +32,10 @@ struct ChatThemeBackground {
int gradientRotation = 0;
bool isPattern = false;
bool tile = false;
[[nodiscard]] bool waitingForNegativePattern() const {
return isPattern && prepared.isNull() && (patternOpacity < 0.);
}
};
bool operator==(const ChatThemeBackground &a, const ChatThemeBackground &b);
@@ -137,6 +141,7 @@ public:
QRect viewport,
QRect clip);
[[nodiscard]] const BackgroundState &backgroundState(QSize area);
void clearBackgroundState();
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
void rotateComplexGradientBackground();
@@ -157,7 +162,6 @@ private:
void cacheBubblesNow();
void cacheBubblesAsync(
const CacheBackgroundRequest &request);
void setCachedBubbles(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest cacheBubblesRequest(
QSize area) const;

View File

@@ -0,0 +1,637 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/chat/choose_theme_controller.h"
#include "ui/rp_widget.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/chat/chat_theme.h"
#include "ui/chat/message_bubble.h"
#include "ui/wrap/vertical_layout.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "data/data_cloud_themes.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "styles/style_widgets.h"
#include "styles/style_layers.h" // boxTitle.
#include "styles/style_settings.h"
#include "styles/style_window.h"
#include <QtWidgets/QApplication>
namespace Ui {
namespace {
constexpr auto kDisableElement = "disable"_cs;
[[nodiscard]] QImage GeneratePreview(not_null<Ui::ChatTheme*> theme) {
const auto &background = theme->background();
const auto &colors = background.colors;
const auto size = st::settingsThemePreviewSize;
auto prepared = background.prepared;
const auto paintPattern = [&](QPainter &p, bool inverted) {
if (prepared.isNull()) {
return;
}
const auto w = prepared.width();
const auto h = prepared.height();
const auto scaled = size.scaled(
st::windowMinWidth / 2,
st::windowMinHeight / 2,
Qt::KeepAspectRatio);
const auto use = (scaled.width() > w || scaled.height() > h)
? scaled.scaled({ w, h }, Qt::KeepAspectRatio)
: scaled;
const auto good = QSize(
std::max(use.width(), 1),
std::max(use.height(), 1));
auto small = prepared.copy(QRect(
QPoint(
(w - good.width()) / 2,
(h - good.height()) / 2),
good));
if (inverted) {
small = Ui::InvertPatternImage(std::move(small));
}
p.drawImage(
QRect(QPoint(), size * style::DevicePixelRatio()),
small);
};
const auto fullsize = size * style::DevicePixelRatio();
auto result = background.waitingForNegativePattern()
? QImage(
fullsize,
QImage::Format_ARGB32_Premultiplied)
: Ui::GenerateBackgroundImage(
fullsize,
colors.empty() ? std::vector{ 1, QColor(0, 0, 0) } : colors,
background.gradientRotation,
background.patternOpacity,
paintPattern);
if (background.waitingForNegativePattern()) {
result.fill(Qt::black);
}
result.setDevicePixelRatio(style::DevicePixelRatio());
{
auto p = QPainter(&result);
const auto sent = QRect(
QPoint(
(size.width()
- st::settingsThemeBubbleSize.width()
- st::settingsThemeBubblePosition.x()),
st::settingsThemeBubblePosition.y()),
st::settingsThemeBubbleSize);
const auto received = QRect(
st::settingsThemeBubblePosition.x(),
sent.y() + sent.height() + st::settingsThemeBubbleSkip,
sent.width(),
sent.height());
const auto radius = st::settingsThemeBubbleRadius;
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
if (const auto pattern = theme->bubblesBackgroundPattern()) {
auto bubble = pattern->pixmap.toImage().scaled(
sent.size() * style::DevicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
const auto corners = Images::CornersMask(radius);
Images::prepareRound(bubble, corners);
p.drawImage(sent, bubble);
} else {
p.setBrush(theme->palette()->msgOutBg()->c);
p.drawRoundedRect(sent, radius, radius);
}
p.setBrush(theme->palette()->msgInBg()->c);
p.drawRoundedRect(received, radius, radius);
}
Images::prepareRound(result, ImageRoundRadius::Large);
return result;
}
[[nodiscard]] QImage GenerateEmptyPreview() {
auto result = QImage(
st::settingsThemePreviewSize * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result.fill(st::settingsThemeNotSupportedBg->c);
result.setDevicePixelRatio(style::DevicePixelRatio());
{
auto p = QPainter(&result);
p.setPen(st::menuIconFg);
p.setFont(st::semiboldFont);
const auto top = st::normalFont->height / 2;
const auto width = st::settingsThemePreviewSize.width();
const auto height = st::settingsThemePreviewSize.height() - top;
p.drawText(
QRect(0, top, width, height),
tr::lng_chat_theme_none(tr::now),
style::al_top);
}
Images::prepareRound(result, ImageRoundRadius::Large);
return result;
}
} // namespace
struct ChooseThemeController::Entry {
uint64 id = 0;
std::shared_ptr<Ui::ChatTheme> theme;
std::shared_ptr<Data::DocumentMedia> media;
QImage preview;
EmojiPtr emoji = nullptr;
QRect geometry;
bool chosen = false;
};
ChooseThemeController::ChooseThemeController(
not_null<RpWidget*> parent,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer)
: _controller(window)
, _peer(peer)
, _wrap(std::make_unique<VerticalLayout>(parent))
, _topShadow(std::make_unique<PlainShadow>(parent))
, _content(_wrap->add(object_ptr<RpWidget>(_wrap.get())))
, _inner(CreateChild<RpWidget>(_content.get()))
, _dark(Window::Theme::IsThemeDarkValue()) {
init(parent->sizeValue());
}
ChooseThemeController::~ChooseThemeController() {
_controller->clearPeerThemeOverride(_peer);
}
void ChooseThemeController::init(rpl::producer<QSize> outer) {
using namespace rpl::mappers;
const auto themes = &_controller->session().data().cloudThemes();
const auto &list = themes->chatThemes();
if (!list.empty()) {
fill(list);
} else {
themes->refreshChatThemes();
themes->chatThemesUpdated(
) | rpl::take(1) | rpl::start_with_next([=] {
fill(themes->chatThemes());
}, lifetime());
}
const auto skip = st::normalFont->spacew * 4;
const auto titleWrap = _wrap->insert(
0,
object_ptr<FixedHeightWidget>(
_wrap.get(),
skip + st::boxTitle.style.font->height + skip));
auto title = CreateChild<FlatLabel>(
titleWrap,
tr::lng_chat_theme_title(),
st::boxTitle);
_wrap->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(_wrap.get()).fillRect(clip, st::windowBg);
}, lifetime());
initButtons();
initList();
_inner->positionValue(
) | rpl::start_with_next([=](QPoint position) {
title->move(std::max(position.x(), skip) + skip, skip);
}, title->lifetime());
std::move(
outer
) | rpl::start_with_next([=](QSize outer) {
_wrap->resizeToWidth(outer.width());
_wrap->move(0, outer.height() - _wrap->height());
const auto line = st::lineWidth;
_topShadow->setGeometry(0, _wrap->y() - line, outer.width(), line);
}, lifetime());
rpl::combine(
_shouldBeShown.value(),
_forceHidden.value(),
_1 && !_2
) | rpl::start_with_next([=](bool shown) {
_wrap->setVisible(shown);
_topShadow->setVisible(shown);
}, lifetime());
}
void ChooseThemeController::initButtons() {
const auto controls = _wrap->add(object_ptr<RpWidget>(_wrap.get()));
const auto cancel = CreateChild<RoundButton>(
controls,
tr::lng_cancel(),
st::defaultLightButton);
const auto apply = CreateChild<RoundButton>(
controls,
tr::lng_chat_theme_apply(),
st::defaultActiveButton);
const auto skip = st::normalFont->spacew * 2;
controls->resize(
skip + cancel->width() + skip + apply->width() + skip,
apply->height() + skip * 2);
rpl::combine(
controls->widthValue(),
cancel->widthValue(),
apply->widthValue()
) | rpl::start_with_next([=](
int outer,
int cancelWidth,
int applyWidth) {
const auto inner = skip + cancelWidth + skip + applyWidth + skip;
const auto left = (outer - inner) / 2;
cancel->moveToLeft(left, 0);
apply->moveToRight(left, 0);
}, controls->lifetime());
const auto changed = [=] {
if (_chosen.isEmpty()) {
return false;
}
const auto now = Ui::Emoji::Find(_peer->themeEmoji());
if (_chosen == kDisableElement.utf16()) {
return !now;
}
for (const auto &entry : _entries) {
if (entry.id && entry.emoji->text() == _chosen) {
return (now != entry.emoji);
}
}
return false;
};
cancel->setClickedCallback([=] { close(); });
apply->setClickedCallback([=] {
if (const auto chosen = findChosen()) {
if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) {
const auto now = chosen->id ? _chosen : QString();
_peer->setThemeEmoji(now);
if (chosen->theme) {
// Remember while changes propagate through event loop.
_controller->pushLastUsedChatTheme(chosen->theme);
}
const auto api = &_peer->session().api();
api->request(MTPmessages_SetChatTheme(
_peer->input,
MTP_string(now)
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result);
}).send();
}
}
_controller->toggleChooseChatTheme(_peer);
});
}
void ChooseThemeController::paintEntry(QPainter &p, const Entry &entry) {
const auto geometry = entry.geometry;
p.drawImage(geometry, entry.preview);
const auto size = Ui::Emoji::GetSizeLarge();
const auto factor = style::DevicePixelRatio();
const auto emojiLeft = geometry.x()
+ (geometry.width() - (size / factor)) / 2;
const auto emojiTop = geometry.y()
+ geometry.height()
- (size / factor)
- (st::normalFont->spacew * 2);
Ui::Emoji::Draw(p, entry.emoji, size, emojiLeft, emojiTop);
if (entry.chosen) {
auto hq = PainterHighQualityEnabler(p);
auto pen = st::activeLineFg->p;
const auto width = st::defaultFlatInput.borderWidth;
pen.setWidth(width);
p.setPen(pen);
const auto add = st::lineWidth + width;
p.drawRoundedRect(
entry.geometry.marginsAdded({ add, add, add, add }),
st::roundRadiusLarge + add,
st::roundRadiusLarge + add);
}
}
void ChooseThemeController::initList() {
_content->resize(
_content->width(),
8 * st::normalFont->spacew + st::settingsThemePreviewSize.height());
_inner->setMouseTracking(true);
_inner->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(_inner.get());
for (const auto &entry : _entries) {
if (entry.preview.isNull() || !clip.intersects(entry.geometry)) {
continue;
}
paintEntry(p, entry);
}
}, lifetime());
const auto byPoint = [=](QPoint position) -> Entry* {
for (auto &entry : _entries) {
if (entry.geometry.contains(position)) {
return &entry;
}
}
return nullptr;
};
const auto chosenText = [=](const Entry *entry) {
if (!entry) {
return QString();
} else if (entry->id) {
return entry->emoji->text();
} else {
return kDisableElement.utf16();
}
};
_inner->events(
) | rpl::start_with_next([=](not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::MouseMove) {
const auto mouse = static_cast<QMouseEvent*>(event.get());
const auto skip = _inner->width() - _content->width();
if (skip <= 0) {
_dragStartPosition = _pressPosition = std::nullopt;
} else if (_pressPosition.has_value()
&& ((mouse->globalPos() - *_pressPosition).manhattanLength()
>= QApplication::startDragDistance())) {
_dragStartPosition = base::take(_pressPosition);
_dragStartInnerLeft = _inner->x();
}
if (_dragStartPosition.has_value()) {
const auto shift = mouse->globalPos().x()
- _dragStartPosition->x();
updateInnerLeft(_dragStartInnerLeft + shift);
} else {
_inner->setCursor(byPoint(mouse->pos())
? style::cur_pointer
: style::cur_default);
}
} else if (type == QEvent::MouseButtonPress) {
const auto mouse = static_cast<QMouseEvent*>(event.get());
if (mouse->button() == Qt::LeftButton) {
_pressPosition = mouse->globalPos();
}
_pressed = chosenText(byPoint(mouse->pos()));
} else if (type == QEvent::MouseButtonRelease) {
_pressPosition = _dragStartPosition = std::nullopt;
const auto mouse = static_cast<QMouseEvent*>(event.get());
const auto entry = byPoint(mouse->pos());
const auto chosen = chosenText(entry);
if (entry && chosen == _pressed && chosen != _chosen) {
clearCurrentBackgroundState();
if (const auto was = findChosen()) {
was->chosen = false;
}
_chosen = chosen;
entry->chosen = true;
if (entry->theme || !entry->id) {
_controller->overridePeerTheme(_peer, entry->theme);
}
_inner->update();
}
_pressed = QString();
} else if (type == QEvent::Wheel) {
const auto wheel = static_cast<QWheelEvent*>(event.get());
const auto was = _inner->x();
updateInnerLeft((wheel->angleDelta().x() != 0)
? (was + (wheel->pixelDelta().x()
? wheel->pixelDelta().x()
: wheel->angleDelta().x()))
: (wheel->angleDelta().y() != 0)
? (was + (wheel->pixelDelta().y()
? wheel->pixelDelta().y()
: wheel->angleDelta().y()))
: was);
}
}, lifetime());
_content->events(
) | rpl::start_with_next([=](not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::KeyPress) {
const auto key = static_cast<QKeyEvent*>(event.get());
if (key->key() == Qt::Key_Escape) {
close();
}
}
}, lifetime());
rpl::combine(
_content->widthValue(),
_inner->widthValue()
) | rpl::start_with_next([=](int content, int inner) {
if (!content || !inner) {
return;
} else if (!_entries.empty() && !_initialInnerLeftApplied) {
applyInitialInnerLeft();
} else {
updateInnerLeft(_inner->x());
}
}, lifetime());
}
void ChooseThemeController::applyInitialInnerLeft() {
if (const auto chosen = findChosen()) {
updateInnerLeft(
_content->width() / 2 - chosen->geometry.center().x());
}
_initialInnerLeftApplied = true;
}
void ChooseThemeController::updateInnerLeft(int now) {
const auto skip = _content->width() - _inner->width();
const auto clamped = (skip >= 0)
? (skip / 2)
: std::clamp(now, skip, 0);
_inner->move(clamped, 0);
}
void ChooseThemeController::close() {
if (const auto chosen = findChosen()) {
if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) {
clearCurrentBackgroundState();
}
}
_controller->toggleChooseChatTheme(_peer);
}
void ChooseThemeController::clearCurrentBackgroundState() {
if (const auto entry = findChosen()) {
if (entry->theme) {
entry->theme->clearBackgroundState();
}
}
}
auto ChooseThemeController::findChosen() -> Entry* {
if (_chosen.isEmpty()) {
return nullptr;
}
for (auto &entry : _entries) {
if (!entry.id && _chosen == kDisableElement.utf16()) {
return &entry;
} else if (_chosen == entry.emoji->text()) {
return &entry;
}
}
return nullptr;
}
auto ChooseThemeController::findChosen() const -> const Entry* {
return const_cast<ChooseThemeController*>(this)->findChosen();
}
void ChooseThemeController::fill(
const std::vector<Data::ChatTheme> &themes) {
if (themes.empty()) {
return;
}
const auto count = int(themes.size()) + 1;
const auto single = st::settingsThemePreviewSize;
const auto skip = st::normalFont->spacew * 2;
const auto full = single.width() * count + skip * (count + 3);
_inner->resize(full, skip + single.height() + skip);
const auto initial = Ui::Emoji::Find(_peer->themeEmoji());
_dark.value(
) | rpl::start_with_next([=](bool dark) {
clearCurrentBackgroundState();
if (_chosen.isEmpty() && initial) {
_chosen = initial->text();
}
_cachingLifetime.destroy();
const auto old = base::take(_entries);
auto x = skip * 2;
_entries.push_back({
.preview = GenerateEmptyPreview(),
.emoji = Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\x8c")),
.geometry = QRect(QPoint(x, skip), single),
.chosen = (_chosen == kDisableElement.utf16()),
});
Assert(_entries.front().emoji != nullptr);
style::PaletteChanged(
) | rpl::start_with_next([=] {
_entries.front().preview = GenerateEmptyPreview();
}, _cachingLifetime);
x += single.width() + skip;
for (const auto &theme : themes) {
const auto emoji = Ui::Emoji::Find(theme.emoticon);
if (!emoji) {
continue;
}
const auto &used = dark ? theme.dark : theme.light;
const auto id = used.id;
const auto isChosen = (_chosen == emoji->text());
_entries.push_back({
.id = id,
.emoji = emoji,
.geometry = QRect(QPoint(x, skip), single),
.chosen = isChosen,
});
_controller->cachedChatThemeValue(
used
) | rpl::filter([=](const std::shared_ptr<ChatTheme> &data) {
return data && (data->key() == id);
}) | rpl::take(
1
) | rpl::start_with_next([=](std::shared_ptr<ChatTheme> &&data) {
const auto id = data->key();
const auto i = ranges::find(_entries, id, &Entry::id);
if (i == end(_entries)) {
return;
}
const auto theme = data.get();
i->theme = std::move(data);
i->preview = GeneratePreview(theme);
if (_chosen == i->emoji->text()) {
_controller->overridePeerTheme(_peer, i->theme);
}
_inner->update();
if (!theme->background().isPattern
|| !theme->background().prepared.isNull()) {
return;
}
// Subscribe to pattern loading if needed.
theme->repaintBackgroundRequests(
) | rpl::filter([=] {
const auto i = ranges::find(
_entries,
id,
&Entry::id);
return (i == end(_entries))
|| !i->theme->background().prepared.isNull();
}) | rpl::take(1) | rpl::start_with_next([=] {
const auto i = ranges::find(
_entries,
id,
&Entry::id);
if (i == end(_entries)) {
return;
}
i->preview = GeneratePreview(theme);
_inner->update();
}, _cachingLifetime);
}, _cachingLifetime);
x += single.width() + skip;
}
if (!_initialInnerLeftApplied && _content->width() > 0) {
applyInitialInnerLeft();
}
}, lifetime());
_shouldBeShown = true;
}
bool ChooseThemeController::shouldBeShown() const {
return _shouldBeShown.current();
}
rpl::producer<bool> ChooseThemeController::shouldBeShownValue() const {
return _shouldBeShown.value();
}
int ChooseThemeController::height() const {
return shouldBeShown() ? _wrap->height() : 0;
}
void ChooseThemeController::hide() {
_forceHidden = true;
}
void ChooseThemeController::show() {
_forceHidden = false;
}
void ChooseThemeController::raise() {
_wrap->raise();
_topShadow->raise();
}
void ChooseThemeController::setFocus() {
_content->setFocus();
}
rpl::lifetime &ChooseThemeController::lifetime() {
return _wrap->lifetime();
}
} // namespace Ui

View File

@@ -0,0 +1,84 @@
/*
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
class PeerData;
namespace Window {
class SessionController;
} // namespace Window
namespace Data {
struct ChatTheme;
} // namespace Data
namespace Ui {
class RpWidget;
class PlainShadow;
class VerticalLayout;
class ChooseThemeController final {
public:
ChooseThemeController(
not_null<RpWidget*> parent,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer);
~ChooseThemeController();
[[nodiscard]] bool shouldBeShown() const;
[[nodiscard]] rpl::producer<bool> shouldBeShownValue() const;
[[nodiscard]] int height() const;
void hide();
void show();
void raise();
void setFocus();
[[nodiscard]] rpl::lifetime &lifetime();
private:
struct Entry;
void init(rpl::producer<QSize> outer);
void initButtons();
void initList();
void fill(const std::vector<Data::ChatTheme> &themes);
void close();
void clearCurrentBackgroundState();
void paintEntry(QPainter &p, const Entry &entry);
void applyInitialInnerLeft();
void updateInnerLeft(int now);
[[nodiscard]] Entry *findChosen();
[[nodiscard]] const Entry *findChosen() const;
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const std::unique_ptr<VerticalLayout> _wrap;
const std::unique_ptr<PlainShadow> _topShadow;
const not_null<RpWidget*> _content;
const not_null<RpWidget*> _inner;
std::vector<Entry> _entries;
QString _pressed;
QString _chosen;
std::optional<QPoint> _pressPosition;
std::optional<QPoint> _dragStartPosition;
int _dragStartInnerLeft = 0;
bool _initialInnerLeftApplied = false;
rpl::variable<bool> _shouldBeShown = false;
rpl::variable<bool> _forceHidden = false;
rpl::variable<bool> _dark = false;
rpl::lifetime _cachingLifetime;
};
} // namespace Ui

View File

@@ -63,7 +63,7 @@ rpl::producer<bool> GroupCallScheduledLeft::late() const {
void GroupCallScheduledLeft::update() {
const auto now = crl::now();
const auto duration = (_datePrecise - now);
const auto left = crl::time(std::round(std::abs(duration) / 1000.));
const auto left = crl::time(base::SafeRound(std::abs(duration) / 1000.));
const auto late = (duration < 0) && (left > 0);
_late = late;
constexpr auto kDay = 24 * 60 * 60;

View File

@@ -1002,7 +1002,7 @@ void CallMuteButton::shake() {
? -1.
: 0.;
const auto shift = from * (1. - part) + to * part;
_labelShakeShift = int(std::round(shift * st::shakeShift));
_labelShakeShift = int(base::SafeRound(shift * st::shakeShift));
updateLabelsGeometry();
};
_shakeAnimation.start(

View File

@@ -214,7 +214,8 @@ Action::Action(
content
) | rpl::start_with_next([=](WhoReadContent &&content) {
checkAppeared();
const auto changed = (_content.participants != content.participants);
const auto changed = (_content.participants != content.participants)
|| (_content.unknown != content.unknown);
_content = content;
if (changed) {
PostponeCall(this, [=] { populateSubmenu(); });

View File

@@ -176,7 +176,7 @@ QPixmap CheckCaches::frame(
auto &frames = framesForStyle(st, displayInactive);
const auto frameCount = int(frames.list.size());
const auto frameIndex = int(std::round(progress * (frameCount - 1)));
const auto frameIndex = int(base::SafeRound(progress * (frameCount - 1)));
Assert(frameIndex >= 0 && frameIndex < frameCount);
if (!frames.list[frameIndex]) {

View File

@@ -44,11 +44,11 @@ void PaintSavedMessagesInner(
// X XX XX X | |
// XX XX --- ---
const auto thinkness = std::round(size * 0.055);
const auto thinkness = base::SafeRound(size * 0.055);
const auto increment = int(thinkness) % 2 + (size % 2);
const auto width = std::round(size * 0.15) * 2 + increment;
const auto height = std::round(size * 0.19) * 2 + increment;
const auto add = std::round(size * 0.064);
const auto width = base::SafeRound(size * 0.15) * 2 + increment;
const auto height = base::SafeRound(size * 0.19) * 2 + increment;
const auto add = base::SafeRound(size * 0.064);
const auto left = x + (size - width) / 2;
const auto top = y + (size - height) / 2;

View File

@@ -11,7 +11,7 @@ namespace Ui {
namespace {
int Round(float64 value) {
return int(std::round(value));
return int(base::SafeRound(value));
}
class Layouter {

View File

@@ -694,8 +694,9 @@ InMemoryKey inMemoryKey(const WebFileLocation &location) {
InMemoryKey inMemoryKey(const GeoPointLocation &location) {
return InMemoryKey(
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(std::round(std::abs(location.lon + 360.) * 1000000)),
(uint64(base::SafeRound(
std::abs(location.lat + 360.) * 1000000)) << 32)
| uint64(base::SafeRound(std::abs(location.lon + 360.) * 1000000)),
(uint64(location.width) << 32) | uint64(location.height));
}

View File

@@ -329,7 +329,7 @@ void MediaSlider::paintEvent(QPaintEvent *e) {
const auto dividerValue = horizontal
? divider.atValue
: (1. - divider.atValue);
const auto dividerMid = std::round(from
const auto dividerMid = base::SafeRound(from
+ dividerValue * length);
const auto &size = divider.size;
const auto rect = horizontal

View File

@@ -167,14 +167,14 @@ public:
}
}
setAdjustCallback([=](float64 value) {
return std::round(value * sectionsCount) / sectionsCount;
return base::SafeRound(value * sectionsCount) / sectionsCount;
});
setChangeProgressCallback([
=,
convert = std::forward<Convert>(convert),
callback = std::forward<Callback>(callback)
](float64 value) {
const auto index = int(std::round(value * sectionsCount));
const auto index = int(base::SafeRound(value * sectionsCount));
callback(convert(index));
});
}

View File

@@ -25,8 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Window {
namespace {
constexpr auto kDarkValueThreshold = 0.5;
[[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
@@ -51,17 +49,9 @@ constexpr auto kDarkValueThreshold = 0.5;
[[nodiscard]] auto MaybeCloudThemeValueFromPeer(
not_null<PeerData*> peer)
-> rpl::producer<std::optional<Data::CloudTheme>> {
auto isThemeDarkValue = rpl::single(
rpl::empty_value()
) | rpl::then(
style::PaletteChanged()
) | rpl::map([] {
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);
}) | rpl::distinct_until_changed();
return rpl::combine(
MaybeChatThemeDataValueFromPeer(peer),
std::move(isThemeDarkValue)
Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
) | rpl::map([](std::optional<Data::ChatTheme> theme, bool night) {
return !theme
? std::nullopt
@@ -304,7 +294,7 @@ auto ChatThemeValueFromPeer(
not_null<SessionController*> controller,
not_null<PeerData*> peer)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
return MaybeCloudThemeValueFromPeer(
auto cloud = MaybeCloudThemeValueFromPeer(
peer
) | rpl::map([=](std::optional<Data::CloudTheme> theme)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
@@ -314,6 +304,17 @@ auto ChatThemeValueFromPeer(
return controller->cachedChatThemeValue(*theme);
}) | rpl::flatten_latest(
) | rpl::distinct_until_changed();
return rpl::combine(
std::move(cloud),
controller->peerThemeOverrideValue()
) | rpl::map([=](
std::shared_ptr<Ui::ChatTheme> &&cloud,
PeerThemeOverride &&overriden) {
return (overriden.peer == peer.get())
? std::move(overriden.theme)
: std::move(cloud);
});
}
} // namespace Window

View File

@@ -46,6 +46,7 @@ namespace {
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs;
constexpr auto kDarkValueThreshold = 0.5;
struct Applying {
Saved data;
@@ -1450,6 +1451,16 @@ bool LoadFromContent(
out);
}
rpl::producer<bool> IsThemeDarkValue() {
return rpl::single(
rpl::empty_value()
) | rpl::then(
style::PaletteChanged()
) | rpl::map([] {
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);
});
}
QString EditingPalettePath() {
return cWorkingDir() + "tdata/editing-theme.tdesktop-palette";
}

View File

@@ -97,6 +97,8 @@ void ResetToSomeDefault();
[[nodiscard]] bool IsNonDefaultBackground();
void Revert();
[[nodiscard]] rpl::producer<bool> IsThemeDarkValue();
[[nodiscard]] QString EditingPalettePath();
// NB! This method looks to Core::App().settings() to get colorizer by 'file'.

View File

@@ -505,7 +505,13 @@ void Generator::paintComposeArea() {
_p->fillRect(_composeArea, st::historyReplyBg[_palette]);
auto controlsTop = _composeArea.y() + _composeArea.height() - st::historySendSize.height();
st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + st::historyAttach.iconPosition.x(), controlsTop + st::historyAttach.iconPosition.y(), _rect.width());
const auto attachIconLeft = (st::historyAttach.iconPosition.x() < 0)
? ((st::historyAttach.width - st::historyAttach.icon.width()) / 2)
: st::historyAttach.iconPosition.x();
const auto attachIconTop = (st::historyAttach.iconPosition.y() < 0)
? ((st::historyAttach.height - st::historyAttach.icon.height()) / 2)
: st::historyAttach.iconPosition.y();
st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + attachIconLeft, controlsTop + attachIconTop, _rect.width());
auto right = st::historySendRight + st::historySendSize.width();
st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height()));

View File

@@ -723,7 +723,7 @@ int CloudList::resizeGetHeight(int newWidth) {
for (const auto &element : _elements) {
const auto button = element.button.get();
button->resizeToWidth(single);
button->moveToLeft(int(std::round(x)), y);
button->moveToLeft(int(base::SafeRound(x)), y);
accumulate_max(rowHeight, button->height());
x += single + skip;
if (++index == kShowPerRow) {

View File

@@ -487,6 +487,11 @@ void Filler::addUserActions(not_null<UserData*> user) {
[=] { AddBotToGroup::Start(user); });
}
addPollAction(user);
if (!user->isBot()) {
_addAction(
tr::lng_chat_theme_change(tr::now),
[=] { controller->toggleChooseChatTheme(user); });
}
if (user->canExportChatHistory()) {
_addAction(
tr::lng_profile_export_chat(tr::now),

View File

@@ -125,6 +125,14 @@ void ActivateWindow(not_null<SessionController*> controller) {
Ui::ActivateWindowDelayed(window);
}
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) {
return (a.peer == b.peer) && (a.theme == b.theme);
}
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b) {
return !(a == b);
}
DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date)
: _chat(chat)
, _date(date) {
@@ -1208,6 +1216,10 @@ void SessionController::clearChooseReportMessages() {
content()->clearChooseReportMessages();
}
void SessionController::toggleChooseChatTheme(not_null<PeerData*> peer) {
content()->toggleChooseChatTheme(peer);
}
void SessionController::updateColumnLayout() {
content()->updateColumnLayout();
}
@@ -1397,7 +1409,7 @@ auto SessionController::cachedChatThemeValue(
const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes)) {
if (auto strong = i->second.theme.lock()) {
pushToLastUsed(strong);
pushLastUsedChatTheme(strong);
return rpl::single(std::move(strong));
}
}
@@ -1413,12 +1425,12 @@ auto SessionController::cachedChatThemeValue(
if (theme->key() != key) {
return false;
}
pushToLastUsed(theme);
pushLastUsedChatTheme(theme);
return true;
}) | rpl::take(limit));
}
void SessionController::pushToLastUsed(
void SessionController::pushLastUsedChatTheme(
const std::shared_ptr<Ui::ChatTheme> &theme) {
const auto i = ranges::find(_lastUsedCustomChatThemes, theme);
if (i == end(_lastUsedCustomChatThemes)) {
@@ -1444,6 +1456,21 @@ void SessionController::clearCachedChatThemes() {
_customChatThemes.clear();
}
void SessionController::overridePeerTheme(
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme) {
_peerThemeOverride = PeerThemeOverride{
peer,
theme ? theme : _defaultChatTheme,
};
}
void SessionController::clearPeerThemeOverride(not_null<PeerData*> peer) {
if (_peerThemeOverride.current().peer == peer.get()) {
_peerThemeOverride = PeerThemeOverride();
}
}
void SessionController::pushDefaultChatBackground() {
const auto background = Theme::Background();
const auto &paper = background->paper();

View File

@@ -75,6 +75,13 @@ enum class GifPauseReason {
using GifPauseReasons = base::flags<GifPauseReason>;
inline constexpr bool is_flag_type(GifPauseReason) { return true; };
struct PeerThemeOverride {
PeerData *peer = nullptr;
std::shared_ptr<Ui::ChatTheme> theme;
};
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b);
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b);
class DateClickHandler : public ClickHandler {
public:
DateClickHandler(Dialogs::Key chat, QDate date);
@@ -378,6 +385,8 @@ public:
Fn<void(MessageIdsList)> done);
void clearChooseReportMessages();
void toggleChooseChatTheme(not_null<PeerData*> peer);
base::Variable<bool> &dialogsListFocused() {
return _dialogsListFocused;
}
@@ -412,6 +421,16 @@ public:
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>>;
void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
void clearCachedChatThemes();
void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
void overridePeerTheme(
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme);
void clearPeerThemeOverride(not_null<PeerData*> peer);
[[nodiscard]] auto peerThemeOverrideValue() const
-> rpl::producer<PeerThemeOverride> {
return _peerThemeOverride.value();
}
struct PaintContextArgs {
not_null<Ui::ChatTheme*> theme;
@@ -464,7 +483,6 @@ private:
[[nodiscard]] Ui::ChatThemeBackgroundData backgroundData(
CachedTheme &theme,
bool generateGradient = true) const;
void pushToLastUsed(const std::shared_ptr<Ui::ChatTheme> &theme);
const not_null<Controller*> _window;
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
@@ -500,6 +518,7 @@ private:
const std::unique_ptr<Ui::ChatStyle> _chatStyle;
std::weak_ptr<Ui::ChatTheme> _chatStyleTheme;
std::deque<std::shared_ptr<Ui::ChatTheme>> _lastUsedCustomChatThemes;
rpl::variable<PeerThemeOverride> _peerThemeOverride;
rpl::lifetime _lifetime;

View File

@@ -26,10 +26,11 @@ SHELL [ "scl", "enable", "devtoolset-9", "--", "bash", "-c" ]
RUN ln -s cmake3 /usr/bin/cmake
ENV LibrariesPath /usr/src/Libraries
ENV HFLAGS "-fstack-protector-all -fstack-clash-protection -fPIC -D_FORTIFY_SOURCE=2"
WORKDIR $LibrariesPath
FROM builder AS patches
RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout 9d2a07ba8b
RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout 872c8dc01c
FROM builder AS extra-cmake-modules
@@ -59,7 +60,7 @@ FROM builder AS zlib
RUN git clone -b v1.2.11 --depth=1 $GIT/madler/zlib.git
WORKDIR zlib
RUN ./configure
RUN CFLAGS=\"-O3 $HFLAGS\" ./configure --static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/zlib-cache" install
@@ -70,7 +71,8 @@ FROM builder AS xz
RUN git clone -b v5.2.5 https://git.tukaani.org/xz.git
WORKDIR xz
RUN cmake3 -B build . -DCMAKE_BUILD_TYPE=Release
RUN CFLAGS=\"$HFLAGS\" \
cmake3 -B build . -DCMAKE_BUILD_TYPE=Release
RUN cmake3 --build build -j$(nproc)
RUN DESTDIR="$LibrariesPath/xz-cache" cmake3 --install build
@@ -98,7 +100,7 @@ RUN git clone -b 0.4.17 --depth=1 $GIT/libproxy/libproxy.git
WORKDIR libproxy
RUN git apply ../patches/libproxy.patch
RUN cmake3 -B build . \
RUN CFLAGS=\"$HFLAGS\" CXXFLAGS=\"$HFLAGS\" cmake3 -B build . \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_DBUS=OFF \
-DWITH_NM=OFF \
@@ -114,7 +116,7 @@ FROM builder AS mozjpeg
RUN git clone -b v4.0.1-rc2 --depth=1 $GIT/mozilla/mozjpeg.git
WORKDIR mozjpeg
RUN cmake3 -B build . \
RUN CFLAGS=\"$HFLAGS\" cmake3 -B build . \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DWITH_JPEG8=ON \
@@ -131,7 +133,7 @@ RUN git clone -b v1.3 --depth=1 $GIT/xiph/opus.git
WORKDIR opus
RUN ./autogen.sh
RUN ./configure
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./configure
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/opus-cache" install
@@ -142,7 +144,7 @@ FROM builder AS rnnoise
RUN git clone -b master --depth=1 $GIT/desktop-app/rnnoise
WORKDIR rnnoise
RUN cmake3 -B build . \
RUN CFLAGS=\"$HFLAGS\" cmake3 -B build . \
-DCMAKE_BUILD_TYPE=Release
RUN cmake3 --build build -j$(nproc)
@@ -158,7 +160,7 @@ FROM builder AS xcb-proto
RUN git clone -b xcb-proto-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/proto/xcbproto.git
WORKDIR xcbproto
RUN ./autogen.sh --enable-static
RUN ./autogen.sh
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-proto-cache" install
@@ -171,7 +173,7 @@ COPY --from=xcb-proto ${LibrariesPath}/xcb-proto-cache /
RUN git clone -b libxcb-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcb.git
WORKDIR libxcb
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-cache" install
@@ -202,7 +204,7 @@ COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache /
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git
WORKDIR libxcb-image
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-image-cache" install
@@ -211,7 +213,7 @@ FROM builder AS xcb-keysyms
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git
WORKDIR libxcb-keysyms
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-keysyms-cache" install
@@ -220,7 +222,7 @@ FROM builder AS xcb-render-util
RUN git clone -b 0.3.9 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git
WORKDIR libxcb-render-util
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-render-util-cache" install
@@ -228,7 +230,7 @@ FROM builder AS libXext
RUN git clone -b libXext-1.3.4 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxext.git
WORKDIR libxext
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXext-cache" install
@@ -239,7 +241,7 @@ FROM builder AS libXtst
RUN git clone -b libXtst-1.2.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxtst.git
WORKDIR libxtst
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXtst-cache" install
@@ -250,7 +252,7 @@ FROM builder AS libXfixes
RUN git clone -b libXfixes-5.0.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxfixes.git
WORKDIR libxfixes
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXfixes-cache" install
@@ -274,7 +276,7 @@ FROM builder AS libXrandr
RUN git clone -b libXrandr-1.5.2 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrandr.git
WORKDIR libxrandr
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXrandr-cache" install
@@ -285,7 +287,7 @@ FROM builder AS libXrender
RUN git clone -b libXrender-0.9.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrender.git
WORKDIR libxrender
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXrender-cache" install
@@ -296,7 +298,7 @@ FROM builder AS libXdamage
RUN git clone -b libXdamage-1.1.5 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxdamage.git
WORKDIR libxdamage
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXdamage-cache" install
@@ -307,7 +309,7 @@ FROM builder AS libXcomposite
RUN git clone -b libXcomposite-0.4.5 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcomposite.git
WORKDIR libxcomposite
RUN ./autogen.sh --enable-static
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libXcomposite-cache" install
@@ -396,7 +398,7 @@ COPY --from=drm ${LibrariesPath}/drm-cache /
RUN git clone -b 2.10.0 --depth=1 $GIT/intel/libva.git
WORKDIR libva
RUN ./autogen.sh \
RUN CFLAGS=\"-g -O2 $HFLAGS\" ./autogen.sh \
--enable-static \
--sysconfdir=/etc \
--with-drivers-path=/usr/lib/dri
@@ -440,8 +442,8 @@ RUN git clone -b release/4.4 --depth=1 $GIT/FFmpeg/FFmpeg.git ffmpeg
WORKDIR ffmpeg
RUN ./configure \
--extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \
--extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \
--extra-cflags=\"-DCONFIG_SAFE_BITSTREAM_READER=1 $HFLAGS\" \
--extra-cxxflags=\"-DCONFIG_SAFE_BITSTREAM_READER=1 $HFLAGS\" \
--disable-debug \
--disable-programs \
--disable-doc \
@@ -553,7 +555,7 @@ ADD https://api.github.com/repos/telegramdesktop/openal-soft/git/refs/heads/fix_
RUN git clone -b fix_pulse_default --depth=1 $GIT/telegramdesktop/openal-soft.git
WORKDIR openal-soft
RUN cmake3 -B build . \
RUN CFLAGS=\"$HFLAGS\" CXXFLAGS=\"$HFLAGS\" cmake3 -B build . \
-DCMAKE_BUILD_TYPE=Release \
-DLIBTYPE:STRING=STATIC \
-DALSOFT_EXAMPLES=OFF \
@@ -627,7 +629,7 @@ RUN git clone -b 2.10.6 --depth=1 $GIT/libsigcplusplus/libsigcplusplus.git
WORKDIR libsigcplusplus
ENV ACLOCAL_PATH="/usr/local/share/aclocal"
RUN NOCONFIGURE=1 ./autogen.sh
RUN ./configure --enable-maintainer-mode --enable-static --disable-documentation
RUN CFLAGS=\"-g -O2 $HFLAGS\" CXXFLAGS=\"-g -O2 $HFLAGS\" ./configure --enable-maintainer-mode --enable-static --disable-documentation
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/libsigcplusplus-cache" install
@@ -646,7 +648,7 @@ WORKDIR glibmm
RUN git apply ../patches/glibmm.patch
ENV ACLOCAL_PATH="/usr/local/share/aclocal"
RUN NOCONFIGURE=1 ./autogen.sh
RUN CC=\"gcc -flto\" CXX=\"g++ -flto\" AR=gcc-ar RANLIB=gcc-ranlib ./configure \
RUN CC=\"gcc -flto $HFLAGS\" CXX=\"g++ -flto $HFLAGS\" AR=gcc-ar RANLIB=gcc-ranlib ./configure \
--enable-maintainer-mode \
--enable-static \
--disable-documentation
@@ -810,7 +812,7 @@ RUN meson build
WORKDIR ../../..
RUN cmake3 -B out/Release . \
RUN CFLAGS=\"$HFLAGS\" CXXFLAGS=\"$HFLAGS\" cmake3 -B out/Release . \
-DCMAKE_BUILD_TYPE=Release \
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF \
-DTG_OWT_SPECIAL_TARGET=linux \
@@ -821,7 +823,8 @@ RUN cmake3 -B out/Release . \
RUN cmake3 --build out/Release -- -j$(nproc)
RUN cmake3 -B out/Debug . \
ENV HFLAGS_DEBUG="-fstack-protector-all -fstack-clash-protection -fPIC"
RUN CFLAGS=\"$HFLAGS_DEBUG\" CXXFLAGS=\"$HFLAGS_DEBUG\" cmake3 -B out/Debug . \
-DCMAKE_BUILD_TYPE=Debug \
-DTG_OWT_SPECIAL_TARGET=linux \
-DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \

View File

@@ -684,6 +684,7 @@ depends:yasm/yasm
""")
stage('openal-soft', """
version: 2
git clone -b wasapi_exact_device_time https://github.com/telegramdesktop/openal-soft.git
cd openal-soft
cd build

View File

@@ -1,7 +1,7 @@
AppVersion 3001003
AppVersion 3001005
AppVersionStrMajor 3.1
AppVersionStrSmall 3.1.3
AppVersionStr 3.1.3
AppVersionStrSmall 3.1.5
AppVersionStr 3.1.5
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 3.1.3.beta
AppVersionOriginal 3.1.5.beta

View File

@@ -1,3 +1,18 @@
3.1.5 beta (28.09.21)
- Choose one of 8 new preset themes for any individual private chat.
- Click on '...' menu > 'Change Colors' to pick a theme.
- Both chat participants will see the same theme in that chat on all their devices.
- Each new theme features colorful gradient message bubbles, beautifully animated backgrounds and unique background patterns.
- All chat themes have day and night versions and will follow your overall dark mode settings.
- Implement main window rounded corners on Windows 11.
- Fix audio capture from AirPods on macOS.
3.1.4 beta (27.09.21)
- Fix crash in network availability init.
- Fix assertion violation after a NaN-resulting std::round call.
3.1.3 beta (27.09.21)
- Fix illegal instruction crash in opus encoder.

2
cmake

Submodule cmake updated: 1dacc0ac4f...18d7c34ce1