From 9cb5423d40d6435d9bdb157a0661ed04b049a362 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Aug 2019 19:03:14 +0300 Subject: [PATCH] Allow changing accent color in default themes. --- .../SourceFiles/settings/settings_chat.cpp | 78 +++++++++++------ .../window/themes/window_theme.cpp | 84 +++++++++++++++---- .../SourceFiles/window/themes/window_theme.h | 29 +++++-- .../window/themes/window_theme_preview.cpp | 10 ++- .../window/themes/window_theme_preview.h | 4 +- 5 files changed, 155 insertions(+), 50 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index ed3d79a58..e00198726 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/background_preview_box.h" #include "boxes/download_path_box.h" #include "boxes/local_storage_box.h" +#include "boxes/edit_color_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/input_fields.h" @@ -89,6 +90,7 @@ public: QColor radiobuttonActive; tr::phrase<> name; QString path; + QColor accentColor; }; DefaultTheme(Scheme scheme, bool checked); @@ -801,7 +803,8 @@ void SetupDefaultThemes(not_null container) { color("d7f0ff"), color("ffffff"), tr::lng_settings_theme_blue, - ":/gui/day-blue.tdesktop-theme" + ":/gui/day-blue.tdesktop-theme", + color("40a7e3") }, Scheme{ Type::Default, @@ -821,7 +824,8 @@ void SetupDefaultThemes(not_null container) { color("6b808d"), color("5ca7d4"), tr::lng_settings_theme_midnight, - ":/gui/night.tdesktop-theme" + ":/gui/night.tdesktop-theme", + color("5288c1") }, Scheme{ Type::NightGreen, @@ -831,7 +835,8 @@ void SetupDefaultThemes(not_null container) { color("6b808d"), color("74bf93"), tr::lng_settings_theme_matrix, - ":/gui/night-green.tdesktop-theme" + ":/gui/night-green.tdesktop-theme", + color("3fc1b0") }, }; const auto chosen = [&] { @@ -847,6 +852,47 @@ void SetupDefaultThemes(not_null container) { return Type(-1); }; const auto group = std::make_shared>(chosen()); + + const auto apply = [=]( + const Scheme &scheme, + const Window::Theme::Colorizer *colorizer = nullptr) { + const auto isNight = [](const Scheme &scheme) { + const auto type = scheme.type; + return (type != Type::DayBlue) && (type != Type::Default); + }; + const auto currentlyIsCustom = (chosen() == Type(-1)); + if (Window::Theme::IsNightMode() == isNight(scheme)) { + Window::Theme::ApplyDefaultWithPath(scheme.path, colorizer); + } else { + Window::Theme::ToggleNightMode(scheme.path, colorizer); + } + if (!currentlyIsCustom) { + Window::Theme::KeepApplied(); + } + }; + const auto applyWithColorize = [=](const Scheme &scheme) { + const auto box = Ui::show(Box( + "Choose accent color", + scheme.accentColor)); + box->setSaveCallback([=](QColor result) { + auto colorizer = Window::Theme::Colorizer(); + colorizer.hueThreshold = 10; + colorizer.saturationThreshold = 10; + colorizer.wasHue = scheme.accentColor.hue(); + colorizer.nowHue = result.hue(); + apply(scheme, &colorizer); + }); + }; + const auto schemeClicked = [=]( + const Scheme &scheme, + Qt::KeyboardModifiers modifiers) { + if (scheme.accentColor.hue() && (modifiers & Qt::ControlModifier)) { + applyWithColorize(scheme); + } else { + apply(scheme); + } + }; + auto buttons = ranges::view::all( schemes ) | ranges::view::transform([&](const Scheme &scheme) { @@ -859,34 +905,14 @@ void SetupDefaultThemes(not_null container) { scheme.name(tr::now), st::settingsTheme, std::move(check)); + result->addClickHandler([=] { + schemeClicked(scheme, result->clickModifiers()); + }); weak->setUpdateCallback([=] { result->update(); }); return result; }) | ranges::to_vector; using Update = const Window::Theme::BackgroundUpdate; - const auto apply = [=](const Scheme &scheme) { - const auto isNight = [](const Scheme &scheme) { - const auto type = scheme.type; - return (type != Type::DayBlue) && (type != Type::Default); - }; - const auto currentlyIsCustom = (chosen() == Type(-1)); - if (Window::Theme::IsNightMode() == isNight(scheme)) { - Window::Theme::ApplyDefaultWithPath(scheme.path); - } else { - Window::Theme::ToggleNightMode(scheme.path); - } - if (!currentlyIsCustom) { - Window::Theme::KeepApplied(); - } - }; - group->setChangedCallback([=](Type type) { - const auto i = ranges::find_if(schemes, [&](const Scheme &scheme) { - return (type == scheme.type && type != chosen()); - }); - if (i != end(schemes)) { - apply(*i); - } - }); base::ObservableViewer( *Window::Theme::Background() ) | rpl::filter([](const Update &update) { diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index daf581689..36bf523b6 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -143,12 +143,44 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName return true; } +void Colorize( + uchar &r, + uchar &g, + uchar &b, + not_null colorizer) { + auto color = QColor(int(r), int(g), int(b)); + auto hue = 0; + auto saturation = 0; + auto value = 0; + color.getHsv(&hue, &saturation, &value); + if ((saturation < colorizer->saturationThreshold) + || (std::abs(hue - colorizer->wasHue) > colorizer->hueThreshold)) { + return; + } + const auto changed = hue + (colorizer->nowHue - colorizer->wasHue); + auto nowR = 0; + auto nowG = 0; + auto nowB = 0; + QColor::fromHsv( + (changed + 360) % 360, + saturation, + value + ).getRgb(&nowR, &nowG, &nowB); + r = uchar(nowR); + g = uchar(nowG); + b = uchar(nowB); +} + enum class SetResult { Ok, Bad, NotFound, }; -SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) { +SetResult setColorSchemeValue( + QLatin1String name, + QLatin1String value, + Instance *out, + const Colorizer *colorizer) { auto result = style::palette::SetResult::Ok; auto size = value.size(); auto data = value.data(); @@ -158,6 +190,9 @@ SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance auto g = readHexUchar(data[3], data[4], error); auto b = readHexUchar(data[5], data[6], error); auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255); + if (colorizer) { + Colorize(r, g, b, colorizer); + } if (error) { LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value)); return SetResult::Ok; @@ -189,13 +224,16 @@ SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance return SetResult::Bad; } -bool loadColorScheme(const QByteArray &content, Instance *out) { +bool loadColorScheme( + const QByteArray &content, + Instance *out, + const Colorizer *colorizer = nullptr) { auto unsupported = QMap(); - return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) { + return ReadPaletteValues(content, [&](QLatin1String name, QLatin1String value) { // Find the named value in the already read unsupported list. value = unsupported.value(value, value); - auto result = setColorSchemeValue(name, value, out); + auto result = setColorSchemeValue(name, value, out, colorizer); if (result == SetResult::Bad) { return false; } else if (result == SetResult::NotFound) { @@ -279,7 +317,11 @@ bool loadBackground(zlib::FileToRead &file, QByteArray *outBackground, bool *out return true; } -bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr) { +bool loadTheme( + const QByteArray &content, + Cached &cache, + Instance *out = nullptr, + const Colorizer *colorizer = nullptr) { cache = Cached(); zlib::FileToRead file(content); @@ -295,7 +337,7 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file.")); return false; } - if (!loadColorScheme(schemeContent, out)) { + if (!loadColorScheme(schemeContent, out, colorizer)) { return false; } Background()->saveAdjustableColors(); @@ -331,7 +373,7 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr } } else { // Looks like it is not a .zip theme. - if (!loadColorScheme(content, out)) { + if (!loadColorScheme(content, out, colorizer)) { return false; } Background()->saveAdjustableColors(); @@ -893,7 +935,9 @@ bool ChatBackground::nightMode() const { return _nightMode; } -void ChatBackground::toggleNightMode(std::optional themePath) { +void ChatBackground::toggleNightMode( + std::optional themePath, + const Colorizer *colorizer) { const auto settingDefault = themePath.has_value(); const auto oldNightMode = _nightMode; const auto newNightMode = !_nightMode; @@ -926,7 +970,7 @@ void ChatBackground::toggleNightMode(std::optional themePath) { path = themePath ? *themePath : (newNightMode ? NightThemePath() : QString()); - ApplyDefaultWithPath(path); + ApplyDefaultWithPath(path, colorizer); } // Theme editor could have already reverted the testing of this toggle. @@ -1005,9 +1049,11 @@ bool Apply(std::unique_ptr preview) { return true; } -void ApplyDefaultWithPath(const QString &themePath) { +void ApplyDefaultWithPath( + const QString &themePath, + const Colorizer *colorizer) { if (!themePath.isEmpty()) { - if (auto preview = PreviewFromFile(themePath)) { + if (auto preview = PreviewFromFile(themePath, colorizer)) { Apply(std::move(preview)); } } else { @@ -1094,21 +1140,27 @@ void SetNightModeValue(bool nightMode) { } void ToggleNightMode() { - Background()->toggleNightMode(std::nullopt); + Background()->toggleNightMode(std::nullopt, nullptr); } -void ToggleNightMode(const QString &path) { - Background()->toggleNightMode(path); +void ToggleNightMode( + const QString &path, + const Colorizer *colorizer) { + Background()->toggleNightMode(path, colorizer); } -bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) { +bool LoadFromFile( + const QString &path, + Instance *out, + QByteArray *outContent, + const Colorizer *colorizer) { *outContent = readThemeContent(path); if (outContent->size() < 4) { LOG(("Theme Error: Could not load theme from %1").arg(path)); return false; } - return loadTheme(*outContent, out->cached, out); + return loadTheme(*outContent, out->cached, out, colorizer); } bool IsPaletteTestingPath(const QString &path) { diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 323c93189..61a6e662c 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -49,20 +49,35 @@ struct Preview { QImage preview; }; +struct Colorizer { + int wasHue = 0; + int nowHue = 0; + int hueThreshold = 0; + int saturationThreshold = 0; +}; + bool Apply(const QString &filepath); bool Apply(std::unique_ptr preview); -void ApplyDefaultWithPath(const QString &themePath); +void ApplyDefaultWithPath( + const QString &themePath, + const Colorizer *colorizer = nullptr); bool ApplyEditedPalette(const QString &path, const QByteArray &content); void KeepApplied(); QString NightThemePath(); [[nodiscard]] bool IsNightMode(); void SetNightModeValue(bool nightMode); void ToggleNightMode(); -void ToggleNightMode(const QString &themePath); +void ToggleNightMode( + const QString &themePath, + const Colorizer *colorizer = nullptr); [[nodiscard]] bool IsNonDefaultBackground(); void Revert(); -bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent); +bool LoadFromFile( + const QString &file, + Instance *out, + QByteArray *outContent, + const Colorizer *colorizer = nullptr); bool IsPaletteTestingPath(const QString &path); QColor CountAverageColor(const QImage &image); QColor AdjustedColor(QColor original, QColor background); @@ -152,7 +167,9 @@ private: void setNightModeValue(bool nightMode); [[nodiscard]] bool nightMode() const; - void toggleNightMode(std::optional themePath); + void toggleNightMode( + std::optional themePath, + const Colorizer *colorizer); void keepApplied(const QString &path, bool write); [[nodiscard]] bool isNonDefaultThemeOrBackground(); [[nodiscard]] bool isNonDefaultBackground(); @@ -162,7 +179,9 @@ private: friend bool IsNightMode(); friend void SetNightModeValue(bool nightMode); friend void ToggleNightMode(); - friend void ToggleNightMode(const QString &themePath); + friend void ToggleNightMode( + const QString &themePath, + const Colorizer *colorizer); friend void KeepApplied(); friend bool IsNonDefaultBackground(); diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index 7e473fc39..e75c09719 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -908,7 +908,9 @@ void Generator::restoreTextPalette() { } // namespace -std::unique_ptr PreviewFromFile(const QString &filepath) { +std::unique_ptr PreviewFromFile( + const QString &filepath, + const Colorizer *colorizer) { auto result = std::make_unique(); result->pathRelative = filepath.isEmpty() ? QString() @@ -916,7 +918,11 @@ std::unique_ptr PreviewFromFile(const QString &filepath) { result->pathAbsolute = filepath.isEmpty() ? QString() : QFileInfo(filepath).absoluteFilePath(); - if (!LoadFromFile(filepath, &result->instance, &result->content)) { + if (!LoadFromFile( + filepath, + &result->instance, + &result->content, + colorizer)) { return nullptr; } return result; diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.h b/Telegram/SourceFiles/window/themes/window_theme_preview.h index adde5109a..067b075db 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.h +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.h @@ -18,7 +18,9 @@ struct CurrentData { bool backgroundTiled = false; }; -std::unique_ptr PreviewFromFile(const QString &filepath); +std::unique_ptr PreviewFromFile( + const QString &filepath, + const Colorizer *colorizer = nullptr); std::unique_ptr GeneratePreview( const QString &filepath, CurrentData &&data);