Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd4a543bab | ||
|
|
d525e56053 | ||
|
|
dab5d1f994 | ||
|
|
de3b52425c | ||
|
|
844fd58a97 | ||
|
|
de2bad51d3 | ||
|
|
1424ea3540 | ||
|
|
a8efd0ef3d | ||
|
|
1204e282d3 | ||
|
|
6588242793 | ||
|
|
b1ba9a42c6 | ||
|
|
ab0d2bf9c6 | ||
|
|
80028e41f3 | ||
|
|
2c581adc55 | ||
|
|
f0e8c1e325 | ||
|
|
a2db9de4d7 | ||
|
|
a228c62286 | ||
|
|
37d940eca6 | ||
|
|
f7c24c54a1 | ||
|
|
19ce1edc16 |
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> ¶ms) {
|
||||
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),
|
||||
});
|
||||
|
||||
@@ -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> ¶ms);
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(' '));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ¶ms,
|
||||
|
||||
@@ -213,6 +213,8 @@ public:
|
||||
Fn<void(MessageIdsList)> done);
|
||||
void clearChooseReportMessages();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
void ui_showPeerHistory(
|
||||
PeerId peer,
|
||||
const SectionShow ¶ms,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) + '%');
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)(
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
637
Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp
Normal file
637
Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp
Normal 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
|
||||
84
Telegram/SourceFiles/ui/chat/choose_theme_controller.h
Normal file
84
Telegram/SourceFiles/ui/chat/choose_theme_controller.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Ui {
|
||||
namespace {
|
||||
|
||||
int Round(float64 value) {
|
||||
return int(std::round(value));
|
||||
return int(base::SafeRound(value));
|
||||
}
|
||||
|
||||
class Layouter {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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'.
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule Telegram/lib_base updated: c42df26ff4...42d4094b1f
Submodule Telegram/lib_lottie updated: d134c0361e...33427eb49c
Submodule Telegram/lib_ui updated: 29cfdad44c...a827d9436e
Submodule Telegram/lib_webrtc updated: 29d5131791...8619486833
Submodule Telegram/lib_webview updated: 3cb51ac668...0a3584b8d8
@@ -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
2
cmake
Submodule cmake updated: 1dacc0ac4f...18d7c34ce1
Reference in New Issue
Block a user