diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index fcc262f174cd3b..0a46da18abbdf2 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -66,6 +66,21 @@ constexpr auto kRecentEmojiLimit = 42; return result; } +[[nodiscard]] quint32 SerializeColor(QColor color) { + return (quint32(std::clamp(color.alpha(), 0, 255)) << 24) + | (quint32(std::clamp(color.red(), 0, 255)) << 16) + | (quint32(std::clamp(color.green(), 0, 255)) << 8) + | quint32(std::clamp(color.blue(), 0, 255)); +} + +[[nodiscard]] QColor DeserializeColor(quint32 value) { + return QColor( + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF, + (value >> 24) & 0xFF); +} + } // namespace Settings::Settings() @@ -236,6 +251,10 @@ QByteArray Settings::serialize() const { for (const auto &id : _accountsOrder) { stream << quint64(id); } + + stream + << SerializeColor(_customAppIcon.color) + << quint64(_customAppIcon.digest); } return result; } @@ -325,6 +344,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 macWarnBeforeQuit = _macWarnBeforeQuit ? 1 : 0; qint32 accountsOrderCount = 0; std::vector accountsOrder; + quint32 customAppIconColor = SerializeColor(_customAppIcon.color); + quint64 customAppIconDigest = _customAppIcon.digest; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -506,6 +527,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + stream + >> customAppIconColor + >> customAppIconDigest; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -662,6 +688,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break; } _macWarnBeforeQuit = macWarnBeforeQuit ? 1 : 0; + _customAppIcon = { DeserializeColor(customAppIconColor), customAppIconDigest }; } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index f411bd4bf49784..4a49699f150399 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -51,6 +51,11 @@ struct WindowPosition { int h = 0; }; +struct CustomAppIcon { + QColor color; + uint64 digest = 0; +}; + class Settings final { public: enum class ScreenCorner { @@ -669,6 +674,12 @@ class Settings final { [[nodiscard]] bool macWarnBeforeQuit() const { return _macWarnBeforeQuit; } + void setCustomAppIcon(CustomAppIcon icon) { + _customAppIcon = icon; + } + [[nodiscard]] CustomAppIcon customAppIcon() const { + return _customAppIcon; + } [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -775,6 +786,7 @@ class Settings final { rpl::variable _playerOrderMode; bool _macWarnBeforeQuit = true; std::vector _accountsOrder; + CustomAppIcon _customAppIcon; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 69a011a025b2ab..21731a804c2944 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -50,6 +50,7 @@ For license and copyright information please follow this link: #include "base/platform/base_platform_info.h" #include "platform/platform_specific.h" #include "base/call_delayed.h" +#include "base/custom_app_icon.h" #include "support/support_common.h" #include "support/support_templates.h" #include "main/main_session.h" @@ -75,6 +76,11 @@ class ColorsPalette final { explicit ColorsPalette(not_null container); void show(Type type); + void show( + std::vector &&colors, + int selected, + int customLightnessMin, + int customLightnessMax); rpl::producer selected() const; @@ -102,11 +108,9 @@ class ColorsPalette final { }; - void show( - not_null scheme, - std::vector &&colors, - int selected); - void selectCustom(not_null scheme); + void selectCustom( + int customLightnessMin, + int customLightnessMax); void updateInnerGeometry(); not_null*> _outer; @@ -267,20 +271,22 @@ void ColorsPalette::show(Type type) { 0, int(list.size()) - 1); - _outer->show(anim::type::instant); - - show(&*scheme, std::move(list), selected); - - const auto inner = _outer->entity(); - inner->resize(_outer->width(), inner->height()); - updateInnerGeometry(); + const auto colorizer = Window::Theme::ColorizerFrom( + *scheme, + scheme->accentColor); + show( + std::move(list), + selected, + colorizer.lightnessMin, + colorizer.lightnessMax); } void ColorsPalette::show( - not_null scheme, std::vector &&colors, - int selected) { - Expects(selected >= 0 && selected < colors.size()); + int selected, + int customLightnessMin, + int customLightnessMax) { + _outer->show(anim::type::instant); while (_buttons.size() > colors.size()) { _buttons.pop_back(); @@ -321,25 +327,27 @@ void ColorsPalette::show( std::move( clicks ) | rpl::start_with_next([=] { - selectCustom(scheme); + selectCustom(customLightnessMin, customLightnessMax); }, inner->lifetime()); } + + inner->resize(_outer->width(), inner->height()); + updateInnerGeometry(); } -void ColorsPalette::selectCustom(not_null scheme) { +void ColorsPalette::selectCustom( + int customLightnessMin, + int customLightnessMax) { const auto selected = ranges::find(_buttons, true, &Button::selected); Assert(selected != end(_buttons)); - const auto colorizer = Window::Theme::ColorizerFrom( - *scheme, - scheme->accentColor); auto box = Box( tr::lng_settings_theme_accent_title(tr::now), EditColorBox::Mode::HSL, (*selected)->color()); box->setLightnessLimits( - colorizer.lightnessMin, - colorizer.lightnessMax); + customLightnessMin, + customLightnessMax); box->setSaveCallback(crl::guard(_outer, [=](QColor result) { _selected.fire_copy(result); })); @@ -1013,6 +1021,88 @@ void SetupChatBackground( }, adaptive->lifetime()); } +#if defined Q_OS_MAC && !defined OS_MAC_STORE +void SetupAppIcon( + not_null window, + not_null container) { + AddSkip(container, st::settingsPrivacySkip); + + container->add( + object_ptr( + container, + rpl::single(u"App Icon"_q), + st::settingsSubsectionTitle), + (st::settingsSubsectionTitlePadding + - QMargins(0, 0, 0, st::settingsSubsectionTitlePadding.bottom()))); + + const auto palette = Ui::CreateChild( + container.get(), + container.get()); + const auto original = QColor(29, 148, 208); + const auto refresh = [=](uint64 currentDigest) { + auto list = Window::Theme::DefaultAccentColors( + Window::Theme::EmbeddedType::Night); + list[0] = original; + const auto current = Core::App().settings().customAppIcon(); + const auto selected = !currentDigest + ? 0 + : (currentDigest != current.digest) + ? -1 + : std::min( + int(ranges::find(list, current.color) - begin(list)), + int(size(list)) - 1); + if (selected == size(list) - 1) { + list[selected] = current.color; + } + palette->show(std::move(list), selected, 0, 255); + }; + refresh(base::CurrentCustomAppIconDigest().value_or(0)); + + const auto logo = QImage(":/gui/art/logo_256.png").convertToFormat( + QImage::Format_ARGB32); + palette->selected( + ) | rpl::start_with_next([=](QColor color) { + if (color == original) { + const auto success = base::ClearCustomAppIcon(); + if (success) { + Core::App().settings().setCustomAppIcon({}); + Core::App().saveSettingsDelayed(); + refresh(0); + } + Ui::Toast::Show(success + ? "Icon cleared. Restarting the Dock." + : "Icon clear failed. See log.txt for details."); + } else { + auto colorizer = style::colorizer{ + .hueThreshold = 64, + }; + original.getHsv( + &colorizer.was.hue, + &colorizer.was.saturation, + &colorizer.was.value); + color.getHsv( + &colorizer.now.hue, + &colorizer.now.saturation, + &colorizer.now.value); + auto image = logo; + style::colorize(image, colorizer); + const auto digest = base::SetCustomAppIcon(std::move(image)); + if (digest) { + Core::App().settings().setCustomAppIcon({ color, *digest }); + Core::App().saveSettingsDelayed(); + refresh(*digest); + } + Ui::Toast::Show(digest + ? "Icon updated. Restarting the Dock." + : "Icon update failed. See log.txt for details."); + } + }, container->lifetime()); + + AddSkip(container); + AddDivider(container); +} +#endif // Q_OS_MAC && !OS_MAC_STORE + void SetupDefaultThemes( not_null window, not_null container) { @@ -1483,6 +1573,10 @@ Chat::Chat(QWidget *parent, not_null controller) void Chat::setupContent(not_null controller) { const auto content = Ui::CreateChild(this); +#if defined Q_OS_MAC && !defined OS_MAC_STORE + SetupAppIcon(&controller->window(), content); +#endif // Q_OS_MAC && !OS_MAC_STORE + SetupThemeOptions(controller, content); SetupAutoNightMode(controller, content); SetupCloudThemes(controller, content); diff --git a/Telegram/lib_base b/Telegram/lib_base index 2aebd29afcad59..2461c5ad4a9298 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 2aebd29afcad5929bde456dfcd3bf71a7bf25760 +Subproject commit 2461c5ad4a929825ff79bc94172334306176f328