Ensure contrast in colorized themes.

This commit is contained in:
John Preston 2019-08-27 16:59:15 +03:00
parent 763bdf8798
commit 117d6192fa
13 changed files with 238 additions and 183 deletions

View file

@ -321,12 +321,14 @@ contactsMultiSelect: MultiSelect {
} }
contactsPhotoCheckIcon: icon {{ contactsPhotoCheckIcon: icon {{
"default_checkbox_check", "default_checkbox_check",
windowFgActive, overviewCheckFgActive,
point(3px, 6px) point(3px, 6px)
}}; }};
contactsPhotoCheck: RoundCheckbox(defaultRoundCheckbox) { contactsPhotoCheck: RoundCheckbox(defaultRoundCheckbox) {
size: 20px; size: 20px;
sizeSmall: 0.3; sizeSmall: 0.3;
bgInactive: overviewCheckBg;
bgActive: overviewCheckBgActive;
check: contactsPhotoCheckIcon; check: contactsPhotoCheckIcon;
} }
contactsPhotoCheckbox: RoundImageCheckbox { contactsPhotoCheckbox: RoundImageCheckbox {

View file

@ -48,8 +48,6 @@ overviewCheck: RoundCheckbox(defaultRoundCheckbox) {
check: icon {{ "overview_photo_check", overviewCheckFgActive, point(4px, 8px) }}; check: icon {{ "overview_photo_check", overviewCheckFgActive, point(4px, 8px) }};
} }
overviewSmallCheck: RoundCheckbox(contactsPhotoCheck) { overviewSmallCheck: RoundCheckbox(contactsPhotoCheck) {
bgInactive: overviewCheckBg;
bgActive: overviewCheckBgActive;
border: overviewCheckBorder; border: overviewCheckBorder;
} }
overviewCheckSkip: 5px; overviewCheckSkip: 5px;

View file

@ -286,7 +286,7 @@ public:
QImage prepareRippleMask() const override; QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override; bool checkRippleStartPosition(QPoint position) const override;
void setColorizer(const Window::Theme::Colorizer *colorizer); void setColorizer(const Window::Theme::Colorizer &colorizer);
private: private:
void checkedChangedHook(anim::type animated) override; void checkedChangedHook(anim::type animated) override;
@ -503,10 +503,10 @@ DefaultTheme::DefaultTheme(Scheme scheme, bool checked)
: AbstractCheckView(st::defaultRadio.duration, checked, nullptr) : AbstractCheckView(st::defaultRadio.duration, checked, nullptr)
, _scheme(scheme) , _scheme(scheme)
, _radio(st::defaultRadio, checked, [=] { update(); }) { , _radio(st::defaultRadio, checked, [=] { update(); }) {
setColorizer(nullptr); setColorizer({});
} }
void DefaultTheme::setColorizer(const Window::Theme::Colorizer *colorizer) { void DefaultTheme::setColorizer(const Window::Theme::Colorizer &colorizer) {
_colorized = _scheme; _colorized = _scheme;
if (colorizer) { if (colorizer) {
Window::Theme::Colorize(_colorized, colorizer); Window::Theme::Colorize(_colorized, colorizer);
@ -986,43 +986,25 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
}; };
const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(chosen()); const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(chosen());
const auto apply = [=]( const auto apply = [=](const Scheme &scheme) {
const Scheme &scheme,
const Window::Theme::Colorizer *colorizer = nullptr) {
const auto isNight = [](const Scheme &scheme) { const auto isNight = [](const Scheme &scheme) {
const auto type = scheme.type; const auto type = scheme.type;
return (type != Type::DayBlue) && (type != Type::Default); return (type != Type::DayBlue) && (type != Type::Default);
}; };
const auto currentlyIsCustom = (chosen() == Type(-1)); const auto currentlyIsCustom = (chosen() == Type(-1));
if (Window::Theme::IsNightMode() == isNight(scheme)) { if (Window::Theme::IsNightMode() == isNight(scheme)) {
Window::Theme::ApplyDefaultWithPath(scheme.path, colorizer); Window::Theme::ApplyDefaultWithPath(scheme.path);
} else { } else {
Window::Theme::ToggleNightMode(scheme.path, colorizer); Window::Theme::ToggleNightMode(scheme.path);
} }
if (!currentlyIsCustom) { if (!currentlyIsCustom) {
Window::Theme::KeepApplied(); Window::Theme::KeepApplied();
} }
}; };
const auto applyWithColor = [=](
const Scheme &scheme,
const QColor &color) {
auto &colors = Core::App().settings().themesAccentColors();
if (colors.get(scheme.type) != color) {
colors.set(scheme.type, color);
Local::writeSettings();
}
const auto colorizer = Window::Theme::ColorizerFrom(scheme, color);
apply(scheme, &colorizer);
};
const auto schemeClicked = [=]( const auto schemeClicked = [=](
const Scheme &scheme, const Scheme &scheme,
Qt::KeyboardModifiers modifiers) { Qt::KeyboardModifiers modifiers) {
const auto &colors = Core::App().settings().themesAccentColors();
if (const auto color = colors.get(scheme.type)) {
applyWithColor(scheme, *color);
} else {
apply(scheme); apply(scheme);
}
}; };
auto checks = base::flat_map<Type,not_null<DefaultTheme*>>(); auto checks = base::flat_map<Type,not_null<DefaultTheme*>>();
@ -1062,9 +1044,9 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
const auto colorizer = Window::Theme::ColorizerFrom( const auto colorizer = Window::Theme::ColorizerFrom(
*scheme, *scheme,
*color); *color);
i->second->setColorizer(&colorizer); i->second->setColorizer(colorizer);
} else { } else {
i->second->setColorizer(nullptr); i->second->setColorizer({});
} }
} }
}; };
@ -1136,7 +1118,12 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
if (scheme == end(kSchemesList)) { if (scheme == end(kSchemesList)) {
return; return;
} }
applyWithColor(*scheme, color); auto &colors = Core::App().settings().themesAccentColors();
if (colors.get(type) != color) {
colors.set(type, color);
Local::writeSettings();
}
apply(*scheme);
}, container->lifetime()); }, container->lifetime());
AddSkip(container); AddSkip(container);

View file

@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "ui/effects/round_checkbox.h" #include "ui/effects/round_checkbox.h"
#include "window/themes/window_theme.h"
#include "ui/rp_widget.h"
namespace Ui { namespace Ui {
namespace { namespace {
@ -237,11 +240,18 @@ QPixmap CheckCaches::paintFrame(
CheckCaches *FrameCaches() { CheckCaches *FrameCaches() {
static QPointer<CheckCaches> Instance; static QPointer<CheckCaches> Instance;
if (auto instance = Instance.data()) { if (const auto instance = Instance.data()) {
return instance; return instance;
} }
auto result = new CheckCaches(QGuiApplication::instance()); const auto result = new CheckCaches(QGuiApplication::instance());
Instance = result; Instance = result;
const auto subscription = Ui::CreateChild<base::Subscription>(result);
*subscription = Window::Theme::Background()->add_subscription([=](
const Window::Theme::BackgroundUpdate &update) {
if (update.paletteChanged()) {
FrameCaches()->clear();
}
});
return result; return result;
} }
@ -330,7 +340,6 @@ void RoundCheckbox::setChecked(bool newChecked, SetStyle speed) {
} }
void RoundCheckbox::invalidateCache() { void RoundCheckbox::invalidateCache() {
FrameCaches()->clear();
if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) { if (!_inactiveCacheBg.isNull() || !_inactiveCacheFg.isNull()) {
prepareInactiveCache(); prepareInactiveCache();
} }

View file

@ -700,7 +700,7 @@ defaultInputField: InputField {
heightMax: 148px; heightMax: 148px;
} }
defaultCheckboxIcon: icon {{ "default_checkbox_check", windowFgActive, point(4px, 7px) }}; defaultCheckboxIcon: icon {{ "default_checkbox_check", overviewCheckFgActive, point(4px, 7px) }};
defaultCheck: Check { defaultCheck: Check {
bg: transparent; bg: transparent;

View file

@ -152,8 +152,8 @@ enum class SetResult {
SetResult setColorSchemeValue( SetResult setColorSchemeValue(
QLatin1String name, QLatin1String name,
QLatin1String value, QLatin1String value,
Instance *out, const Colorizer &colorizer,
const Colorizer *colorizer) { Instance *out) {
auto result = style::palette::SetResult::Ok; auto result = style::palette::SetResult::Ok;
auto size = value.size(); auto size = value.size();
auto data = value.data(); auto data = value.data();
@ -163,8 +163,8 @@ SetResult setColorSchemeValue(
auto g = readHexUchar(data[3], data[4], error); auto g = readHexUchar(data[3], data[4], error);
auto b = readHexUchar(data[5], data[6], error); auto b = readHexUchar(data[5], data[6], error);
auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255); auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
if (colorizer && !colorizer->ignoreKeys.contains(name)) { if (colorizer) {
Colorize(r, g, b, colorizer); Colorize(name, r, g, b, colorizer);
} }
if (error) { 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)); 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));
@ -199,14 +199,14 @@ SetResult setColorSchemeValue(
bool loadColorScheme( bool loadColorScheme(
const QByteArray &content, const QByteArray &content,
Instance *out, const Colorizer &colorizer,
const Colorizer *colorizer = nullptr) { Instance *out) {
auto unsupported = QMap<QLatin1String, QLatin1String>(); auto unsupported = QMap<QLatin1String, QLatin1String>();
return ReadPaletteValues(content, [&](QLatin1String name, QLatin1String value) { return ReadPaletteValues(content, [&](QLatin1String name, QLatin1String value) {
// Find the named value in the already read unsupported list. // Find the named value in the already read unsupported list.
value = unsupported.value(value, value); value = unsupported.value(value, value);
auto result = setColorSchemeValue(name, value, out, colorizer); auto result = setColorSchemeValue(name, value, colorizer, out);
if (result == SetResult::Bad) { if (result == SetResult::Bad) {
return false; return false;
} else if (result == SetResult::NotFound) { } else if (result == SetResult::NotFound) {
@ -293,8 +293,8 @@ bool loadBackground(zlib::FileToRead &file, QByteArray *outBackground, bool *out
bool loadTheme( bool loadTheme(
const QByteArray &content, const QByteArray &content,
Cached &cache, Cached &cache,
Instance *out = nullptr, const Colorizer &colorizer,
const Colorizer *colorizer = nullptr) { Instance *out = nullptr) {
cache = Cached(); cache = Cached();
zlib::FileToRead file(content); zlib::FileToRead file(content);
@ -310,7 +310,7 @@ bool loadTheme(
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file.")); LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file."));
return false; return false;
} }
if (!loadColorScheme(schemeContent, out, colorizer)) { if (!loadColorScheme(schemeContent, colorizer, out)) {
return false; return false;
} }
Background()->saveAdjustableColors(); Background()->saveAdjustableColors();
@ -349,7 +349,7 @@ bool loadTheme(
} }
} else { } else {
// Looks like it is not a .zip theme. // Looks like it is not a .zip theme.
if (!loadColorScheme(content, out, colorizer)) { if (!loadColorScheme(content, colorizer, out)) {
return false; return false;
} }
Background()->saveAdjustableColors(); Background()->saveAdjustableColors();
@ -911,9 +911,7 @@ bool ChatBackground::nightMode() const {
return _nightMode; return _nightMode;
} }
void ChatBackground::toggleNightMode( void ChatBackground::toggleNightMode(std::optional<QString> themePath) {
std::optional<QString> themePath,
const Colorizer *colorizer) {
const auto settingDefault = themePath.has_value(); const auto settingDefault = themePath.has_value();
const auto oldNightMode = _nightMode; const auto oldNightMode = _nightMode;
const auto newNightMode = !_nightMode; const auto newNightMode = !_nightMode;
@ -935,6 +933,7 @@ void ChatBackground::toggleNightMode(
const auto loaded = loadTheme( const auto loaded = loadTheme(
preview->content, preview->content,
preview->instance.cached, preview->instance.cached,
ColorizerForTheme(path),
&preview->instance); &preview->instance);
if (!loaded) { if (!loaded) {
return false; return false;
@ -946,7 +945,7 @@ void ChatBackground::toggleNightMode(
path = themePath path = themePath
? *themePath ? *themePath
: (newNightMode ? NightThemePath() : QString()); : (newNightMode ? NightThemePath() : QString());
ApplyDefaultWithPath(path, colorizer); ApplyDefaultWithPath(path);
} }
// Theme editor could have already reverted the testing of this toggle. // Theme editor could have already reverted the testing of this toggle.
@ -993,7 +992,8 @@ bool Load(Saved &&saved) {
return true; return true;
} }
if (!loadTheme(saved.content, saved.cache)) { const auto colorizer = ColorizerForTheme(saved.pathAbsolute);
if (!loadTheme(saved.content, saved.cache, colorizer)) {
return false; return false;
} }
Local::writeTheme(saved); Local::writeTheme(saved);
@ -1025,11 +1025,9 @@ bool Apply(std::unique_ptr<Preview> preview) {
return true; return true;
} }
void ApplyDefaultWithPath( void ApplyDefaultWithPath(const QString &themePath) {
const QString &themePath,
const Colorizer *colorizer) {
if (!themePath.isEmpty()) { if (!themePath.isEmpty()) {
if (auto preview = PreviewFromFile(themePath, colorizer)) { if (auto preview = PreviewFromFile(themePath)) {
Apply(std::move(preview)); Apply(std::move(preview));
} }
} else { } else {
@ -1046,12 +1044,14 @@ void ApplyDefaultWithPath(
bool ApplyEditedPalette(const QString &path, const QByteArray &content) { bool ApplyEditedPalette(const QString &path, const QByteArray &content) {
Instance out; Instance out;
if (!loadColorScheme(content, &out)) { if (!loadColorScheme(content, Colorizer(), &out)) {
return false; return false;
} }
out.cached.colors = out.palette.save(); out.cached.colors = out.palette.save();
out.cached.paletteChecksum = style::palette::Checksum(); out.cached.paletteChecksum = style::palette::Checksum();
out.cached.contentChecksum = hashCrc32(content.constData(), content.size()); out.cached.contentChecksum = hashCrc32(
content.constData(),
content.size());
GlobalApplying.pathRelative = path.isEmpty() GlobalApplying.pathRelative = path.isEmpty()
? QString() ? QString()
@ -1116,27 +1116,25 @@ void SetNightModeValue(bool nightMode) {
} }
void ToggleNightMode() { void ToggleNightMode() {
Background()->toggleNightMode(std::nullopt, nullptr); Background()->toggleNightMode(std::nullopt);
} }
void ToggleNightMode( void ToggleNightMode(const QString &path) {
const QString &path, Background()->toggleNightMode(path);
const Colorizer *colorizer) {
Background()->toggleNightMode(path, colorizer);
} }
bool LoadFromFile( bool LoadFromFile(
const QString &path, const QString &path,
Instance *out, Instance *out,
QByteArray *outContent, QByteArray *outContent) {
const Colorizer *colorizer) {
*outContent = readThemeContent(path); *outContent = readThemeContent(path);
if (outContent->size() < 4) { if (outContent->size() < 4) {
LOG(("Theme Error: Could not load theme from %1").arg(path)); LOG(("Theme Error: Could not load theme from %1").arg(path));
return false; return false;
} }
return loadTheme(*outContent, out->cached, out, colorizer); const auto colorizer = ColorizerForTheme(path);
return loadTheme(*outContent, out->cached, colorizer, out);
} }
bool IsPaletteTestingPath(const QString &path) { bool IsPaletteTestingPath(const QString &path) {

View file

@ -18,8 +18,6 @@ namespace Theme {
constexpr auto kThemeSchemeSizeLimit = 1024 * 1024; constexpr auto kThemeSchemeSizeLimit = 1024 * 1024;
struct Colorizer;
struct Cached { struct Cached {
QByteArray colors; QByteArray colors;
QByteArray background; QByteArray background;
@ -53,26 +51,21 @@ struct Preview {
bool Apply(const QString &filepath); bool Apply(const QString &filepath);
bool Apply(std::unique_ptr<Preview> preview); bool Apply(std::unique_ptr<Preview> preview);
void ApplyDefaultWithPath( void ApplyDefaultWithPath(const QString &themePath);
const QString &themePath,
const Colorizer *colorizer = nullptr);
bool ApplyEditedPalette(const QString &path, const QByteArray &content); bool ApplyEditedPalette(const QString &path, const QByteArray &content);
void KeepApplied(); void KeepApplied();
QString NightThemePath(); QString NightThemePath();
[[nodiscard]] bool IsNightMode(); [[nodiscard]] bool IsNightMode();
void SetNightModeValue(bool nightMode); void SetNightModeValue(bool nightMode);
void ToggleNightMode(); void ToggleNightMode();
void ToggleNightMode( void ToggleNightMode(const QString &themePath);
const QString &themePath,
const Colorizer *colorizer = nullptr);
[[nodiscard]] bool IsNonDefaultBackground(); [[nodiscard]] bool IsNonDefaultBackground();
void Revert(); void Revert();
bool LoadFromFile( bool LoadFromFile(
const QString &file, const QString &file,
Instance *out, Instance *out,
QByteArray *outContent, QByteArray *outContent);
const Colorizer *colorizer = nullptr);
bool IsPaletteTestingPath(const QString &path); bool IsPaletteTestingPath(const QString &path);
QColor CountAverageColor(const QImage &image); QColor CountAverageColor(const QImage &image);
QColor AdjustedColor(QColor original, QColor background); QColor AdjustedColor(QColor original, QColor background);
@ -162,9 +155,7 @@ private:
void setNightModeValue(bool nightMode); void setNightModeValue(bool nightMode);
[[nodiscard]] bool nightMode() const; [[nodiscard]] bool nightMode() const;
void toggleNightMode( void toggleNightMode(std::optional<QString> themePath);
std::optional<QString> themePath,
const Colorizer *colorizer);
void keepApplied(const QString &path, bool write); void keepApplied(const QString &path, bool write);
[[nodiscard]] bool isNonDefaultThemeOrBackground(); [[nodiscard]] bool isNonDefaultThemeOrBackground();
[[nodiscard]] bool isNonDefaultBackground(); [[nodiscard]] bool isNonDefaultBackground();
@ -174,9 +165,7 @@ private:
friend bool IsNightMode(); friend bool IsNightMode();
friend void SetNightModeValue(bool nightMode); friend void SetNightModeValue(bool nightMode);
friend void ToggleNightMode(); friend void ToggleNightMode();
friend void ToggleNightMode( friend void ToggleNightMode(const QString &themePath);
const QString &themePath,
const Colorizer *colorizer);
friend void KeepApplied(); friend void KeepApplied();
friend bool IsNonDefaultBackground(); friend bool IsNonDefaultBackground();

View file

@ -188,7 +188,7 @@ QByteArray replaceValueInContent(const QByteArray &content, const QByteArray &na
QByteArray ColorizeInContent( QByteArray ColorizeInContent(
QByteArray content, QByteArray content,
not_null<const Colorizer*> colorizer) { const Colorizer &colorizer) {
auto validNames = OrderedSet<QLatin1String>(); auto validNames = OrderedSet<QLatin1String>();
content.detach(); content.detach();
auto start = content.constBegin(), data = start, end = data + content.size(); auto start = content.constBegin(), data = start, end = data + content.size();
@ -349,21 +349,10 @@ bool CopyColorsToPalette(
return false; return false;
} }
if (themePath.startsWith(qstr(":/gui"))) { if (const auto colorizer = ColorizerForTheme(themePath)) {
const auto schemes = EmbeddedThemes();
const auto i = ranges::find(
schemes,
themePath,
&EmbeddedScheme::path);
if (i != end(schemes)) {
const auto &colors = Core::App().settings().themesAccentColors();
if (const auto accent = colors.get(i->type)) {
const auto colorizer = ColorizerFrom(*i, *accent);
paletteContent = ColorizeInContent( paletteContent = ColorizeInContent(
std::move(paletteContent), std::move(paletteContent),
&colorizer); colorizer);
}
}
} }
if (f.write(paletteContent) != paletteContent.size()) { if (f.write(paletteContent) != paletteContent.size()) {
LOG(("Theme Error: could not write palette to '%1'").arg(destination)); LOG(("Theme Error: could not write palette to '%1'").arg(destination));

View file

@ -404,13 +404,16 @@ void EditorBlock::sortByDistance(const QColor &to) {
auto fromSaturation = int(); auto fromSaturation = int();
auto fromLightness = int(); auto fromLightness = int();
row.value().getHsl(&fromHue, &fromSaturation, &fromLightness); row.value().getHsl(&fromHue, &fromSaturation, &fromLightness);
if (!row.copyOf().isEmpty() && row.copyOf() != "windowBgActive") { if (!row.copyOf().isEmpty()) {
return 365; return 365;
} }
const auto a = std::abs(fromHue - toHue); const auto a = std::abs(fromHue - toHue);
const auto b = 360 + fromHue - toHue; const auto b = 360 + fromHue - toHue;
const auto c = 360 + toHue - fromHue; const auto c = 360 + toHue - fromHue;
return std::min(a, std::min(b, c)); if (std::min(a, std::min(b, c)) > 15) {
return 363;
}
return 255 - fromSaturation;
}); });
} }

View file

@ -908,9 +908,7 @@ void Generator::restoreTextPalette() {
} // namespace } // namespace
std::unique_ptr<Preview> PreviewFromFile( std::unique_ptr<Preview> PreviewFromFile(const QString &filepath) {
const QString &filepath,
const Colorizer *colorizer) {
auto result = std::make_unique<Preview>(); auto result = std::make_unique<Preview>();
result->pathRelative = filepath.isEmpty() result->pathRelative = filepath.isEmpty()
? QString() ? QString()
@ -918,11 +916,7 @@ std::unique_ptr<Preview> PreviewFromFile(
result->pathAbsolute = filepath.isEmpty() result->pathAbsolute = filepath.isEmpty()
? QString() ? QString()
: QFileInfo(filepath).absoluteFilePath(); : QFileInfo(filepath).absoluteFilePath();
if (!LoadFromFile( if (!LoadFromFile(filepath, &result->instance, &result->content)) {
filepath,
&result->instance,
&result->content,
colorizer)) {
return nullptr; return nullptr;
} }
return result; return result;

View file

@ -18,9 +18,7 @@ struct CurrentData {
bool backgroundTiled = false; bool backgroundTiled = false;
}; };
std::unique_ptr<Preview> PreviewFromFile( std::unique_ptr<Preview> PreviewFromFile(const QString &filepath);
const QString &filepath,
const Colorizer *colorizer = nullptr);
std::unique_ptr<Preview> GeneratePreview( std::unique_ptr<Preview> GeneratePreview(
const QString &filepath, const QString &filepath,
CurrentData &&data); CurrentData &&data);

View file

@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "storage/serialize_common.h" #include "storage/serialize_common.h"
#include "core/application.h"
#include "core/core_settings.h"
namespace Window { namespace Window {
namespace Theme { namespace Theme {
namespace { namespace {
constexpr auto kMaxAccentColors = 3; constexpr auto kMaxAccentColors = 3;
constexpr auto kEnoughLightnessForContrast = 64;
const auto kColorizeIgnoredKeys = base::flat_set<QLatin1String>{ { const auto kColorizeIgnoredKeys = base::flat_set<QLatin1String>{ {
qstr("boxTextFgGood"), qstr("boxTextFgGood"),
@ -100,6 +103,7 @@ Colorizer::Color cColor(str_const hex) {
Colorizer ColorizerFrom(const EmbeddedScheme &scheme, const QColor &color) { Colorizer ColorizerFrom(const EmbeddedScheme &scheme, const QColor &color) {
using Color = Colorizer::Color; using Color = Colorizer::Color;
using Pair = std::pair<Color, Color>;
auto result = Colorizer(); auto result = Colorizer();
result.ignoreKeys = kColorizeIgnoredKeys; result.ignoreKeys = kColorizeIgnoredKeys;
@ -114,26 +118,34 @@ Colorizer ColorizerFrom(const EmbeddedScheme &scheme, const QColor &color) {
&result.now.lightness); &result.now.lightness);
switch (scheme.type) { switch (scheme.type) {
case EmbeddedType::DayBlue: case EmbeddedType::DayBlue:
result.lightnessMax = 191; result.lightnessMax = 160;
break; break;
case EmbeddedType::Night: case EmbeddedType::Night:
result.keepContrast = base::flat_map<QLatin1String, Color>{ { result.keepContrast = base::flat_map<QLatin1String, Pair>{ {
{ qstr("windowFgActive"), cColor("5288c1") }, // windowBgActive //{ qstr("windowFgActive"), Pair{ cColor("5288c1"), cColor("17212b") } }, // windowBgActive
{ qstr("activeButtonFg"), cColor("2f6ea5") }, // activeButtonBg { qstr("activeButtonFg"), Pair{ cColor("2f6ea5"), cColor("17212b") } }, // activeButtonBg
{ qstr("profileVerifiedCheckFg"), cColor("5288c1") }, // profileVerifiedCheckBg { qstr("profileVerifiedCheckFg"), Pair{ cColor("5288c1"), cColor("17212b") } }, // profileVerifiedCheckBg
{ qstr("overviewCheckFgActive"), cColor("5288c1") }, // overviewCheckBgActive { qstr("overviewCheckFgActive"), Pair{ cColor("5288c1"), cColor("17212b") } }, // overviewCheckBgActive
{ qstr("historyFileInIconFg"), Pair{ cColor("3f96d0"), cColor("182533") } }, // msgFileInBg, msgInBg
{ qstr("historyFileInIconFgSelected"), Pair{ cColor("6ab4f4"), cColor("2e70a5") } }, // msgFileInBgSelected, msgInBgSelected
{ qstr("historyFileInRadialFg"), Pair{ cColor("3f96d0"), cColor("182533") } }, // msgFileInBg, msgInBg
{ qstr("historyFileInRadialFgSelected"), Pair{ cColor("6ab4f4"), cColor("2e70a5") } }, // msgFileInBgSelected, msgInBgSelected
{ qstr("historyFileOutIconFg"), Pair{ cColor("4c9ce2"), cColor("2b5278") } }, // msgFileOutBg, msgOutBg
{ qstr("historyFileOutIconFgSelected"), Pair{ cColor("58abf3"), cColor("2e70a5") } }, // msgFileOutBgSelected, msgOutBgSelected
{ qstr("historyFileOutRadialFg"), Pair{ cColor("4c9ce2"), cColor("2b5278") } }, // msgFileOutBg, msgOutBg
{ qstr("historyFileOutRadialFgSelected"), Pair{ cColor("58abf3"), cColor("2e70a5") } }, // msgFileOutBgSelected, msgOutBgSelected
} }; } };
result.lightnessMin = 64; result.lightnessMin = 96;
break; break;
case EmbeddedType::NightGreen: case EmbeddedType::NightGreen:
result.keepContrast = base::flat_map<QLatin1String, Color>{ { result.keepContrast = base::flat_map<QLatin1String, Pair>{ {
{ qstr("windowFgActive"), cColor("3fc1b0") }, // windowBgActive //{ qstr("windowFgActive"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // windowBgActive, windowBg
{ qstr("activeButtonFg"), cColor("2da192") }, // activeButtonBg { qstr("activeButtonFg"), Pair{ cColor("2da192"), cColor("282e33") } }, // activeButtonBg, windowBg
{ qstr("profileVerifiedCheckFg"), cColor("3fc1b0") }, // profileVerifiedCheckBg { qstr("profileVerifiedCheckFg"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // profileVerifiedCheckBg, windowBg
{ qstr("overviewCheckFgActive"), cColor("3fc1b0") }, // overviewCheckBgActive { qstr("overviewCheckFgActive"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // overviewCheckBgActive
{ qstr("callIconFg"), cColor("5ad1c1") }, // callAnswerBg { qstr("callIconFg"), Pair{ cColor("5ad1c1"), cColor("26282c") } }, // callAnswerBg, callBg
} }; } };
result.lightnessMin = 64; result.lightnessMin = 96;
break; break;
} }
result.now.lightness = std::clamp( result.now.lightness = std::clamp(
@ -143,56 +155,131 @@ Colorizer ColorizerFrom(const EmbeddedScheme &scheme, const QColor &color) {
return result; return result;
} }
void Colorize( Colorizer ColorizerForTheme(const QString &absolutePath) {
uchar &r, if (!absolutePath.startsWith(qstr(":/gui"))) {
uchar &g, return Colorizer();
uchar &b, }
not_null<const Colorizer*> colorizer) { const auto schemes = EmbeddedThemes();
auto color = QColor(int(r), int(g), int(b)); const auto i = ranges::find(
schemes,
absolutePath,
&EmbeddedScheme::path);
if (i == end(schemes)) {
return Colorizer();
}
const auto &colors = Core::App().settings().themesAccentColors();
if (const auto accent = colors.get(i->type)) {
return ColorizerFrom(*i, *accent);
}
return Colorizer();
}
[[nodiscard]] std::optional<Colorizer::Color> Colorize(
const Colorizer::Color &color,
const Colorizer &colorizer) {
const auto changeColor = std::abs(color.hue - colorizer.was.hue)
< colorizer.hueThreshold;
if (!changeColor) {
return std::nullopt;
}
const auto nowHue = color.hue + (colorizer.now.hue - colorizer.was.hue);
const auto nowSaturation = ((color.saturation > colorizer.was.saturation)
&& (colorizer.now.saturation > colorizer.was.saturation))
? (((colorizer.now.saturation * (255 - colorizer.was.saturation))
+ ((color.saturation - colorizer.was.saturation)
* (255 - colorizer.now.saturation)))
/ (255 - colorizer.was.saturation))
: ((color.saturation != colorizer.was.saturation)
&& (colorizer.was.saturation != 0))
? ((color.saturation * colorizer.now.saturation)
/ colorizer.was.saturation)
: colorizer.now.saturation;
const auto nowLightness = (color.lightness > colorizer.was.lightness)
? (((colorizer.now.lightness * (255 - colorizer.was.lightness))
+ ((color.lightness - colorizer.was.lightness)
* (255 - colorizer.now.lightness)))
/ (255 - colorizer.was.lightness))
: (color.lightness < colorizer.was.lightness)
? ((color.lightness * colorizer.now.lightness)
/ colorizer.was.lightness)
: colorizer.now.lightness;
return Colorizer::Color{
((nowHue + 360) % 360),
nowSaturation,
nowLightness
};
}
[[nodiscard]] std::optional<QColor> Colorize(
const QColor &color,
const Colorizer &colorizer) {
auto hue = 0; auto hue = 0;
auto saturation = 0; auto saturation = 0;
auto lightness = 0; auto lightness = 0;
color.getHsl(&hue, &saturation, &lightness); color.getHsl(&hue, &saturation, &lightness);
const auto changeColor = std::abs(hue - colorizer->was.hue) const auto result = Colorize(
<= colorizer->hueThreshold; Colorizer::Color{ hue, saturation, lightness },
if (!changeColor) { colorizer);
return; if (!result) {
return std::nullopt;
} }
const auto nowHue = hue + (colorizer->now.hue - colorizer->was.hue); const auto &fields = *result;
const auto nowSaturation = ((saturation > colorizer->was.saturation) return QColor::fromHsl(fields.hue, fields.saturation, fields.lightness);
&& (colorizer->now.saturation > colorizer->was.saturation)) }
? (((colorizer->now.saturation * (255 - colorizer->was.saturation))
+ ((saturation - colorizer->was.saturation) void FillColorizeResult(uchar &r, uchar &g, uchar &b, const QColor &color) {
* (255 - colorizer->now.saturation)))
/ (255 - colorizer->was.saturation))
: ((saturation != colorizer->was.saturation)
&& (colorizer->was.saturation != 0))
? ((saturation * colorizer->now.saturation)
/ colorizer->was.saturation)
: colorizer->now.saturation;
const auto nowLightness = (lightness > colorizer->was.lightness)
? (((colorizer->now.lightness * (255 - colorizer->was.lightness))
+ ((lightness - colorizer->was.lightness)
* (255 - colorizer->now.lightness)))
/ (255 - colorizer->was.lightness))
: (lightness < colorizer->was.lightness)
? ((lightness * colorizer->now.lightness)
/ colorizer->was.lightness)
: colorizer->now.lightness;
auto nowR = 0; auto nowR = 0;
auto nowG = 0; auto nowG = 0;
auto nowB = 0; auto nowB = 0;
QColor::fromHsl( color.getRgb(&nowR, &nowG, &nowB);
((nowHue + 360) % 360),
nowSaturation,
nowLightness
).getRgb(&nowR, &nowG, &nowB);
r = uchar(nowR); r = uchar(nowR);
g = uchar(nowG); g = uchar(nowG);
b = uchar(nowB); b = uchar(nowB);
} }
void Colorize(uint32 &pixel, not_null<const Colorizer*> colorizer) { void Colorize(uchar &r, uchar &g, uchar &b, const Colorizer &colorizer) {
const auto changed = Colorize(QColor(int(r), int(g), int(b)), colorizer);
if (changed) {
FillColorizeResult(r, g, b, *changed);
}
}
void Colorize(
QLatin1String name,
uchar &r,
uchar &g,
uchar &b,
const Colorizer &colorizer) {
if (colorizer.ignoreKeys.contains(name)) {
return;
}
const auto i = colorizer.keepContrast.find(name);
if (i == end(colorizer.keepContrast)) {
Colorize(r, g, b, colorizer);
return;
}
const auto check = i->second.first;
const auto rgb = QColor(int(r), int(g), int(b));
const auto changed = Colorize(rgb, colorizer);
const auto checked = Colorize(check, colorizer).value_or(check);
const auto delta = std::abs(changed.value_or(rgb).lightness() - checked.lightness);
if (delta >= kEnoughLightnessForContrast) {
if (changed) {
FillColorizeResult(r, g, b, *changed);
}
return;
}
const auto replace = i->second.second;
const auto result = Colorize(replace, colorizer).value_or(replace);
FillColorizeResult(
r,
g,
b,
QColor::fromHsl(result.hue, result.saturation, result.lightness));
}
void Colorize(uint32 &pixel, const Colorizer &colorizer) {
const auto chars = reinterpret_cast<uchar*>(&pixel); const auto chars = reinterpret_cast<uchar*>(&pixel);
Colorize( Colorize(
chars[2], chars[2],
@ -201,15 +288,7 @@ void Colorize(uint32 &pixel, not_null<const Colorizer*> colorizer) {
colorizer); colorizer);
} }
void Colorize(QColor &color, not_null<const Colorizer*> colorizer) { void Colorize(QImage &image, const Colorizer &colorizer) {
auto r = uchar(color.red());
auto g = uchar(color.green());
auto b = uchar(color.blue());
Colorize(r, g, b, colorizer);
color = QColor(r, g, b, color.alpha());
}
void Colorize(QImage &image, not_null<const Colorizer*> colorizer) {
image = std::move(image).convertToFormat(QImage::Format_ARGB32); image = std::move(image).convertToFormat(QImage::Format_ARGB32);
const auto bytes = image.bits(); const auto bytes = image.bits();
const auto bytesPerLine = image.bytesPerLine(); const auto bytesPerLine = image.bytesPerLine();
@ -223,7 +302,7 @@ void Colorize(QImage &image, not_null<const Colorizer*> colorizer) {
} }
} }
void Colorize(EmbeddedScheme &scheme, not_null<const Colorizer*> colorizer) { void Colorize(EmbeddedScheme &scheme, const Colorizer &colorizer) {
const auto colors = { const auto colors = {
&EmbeddedScheme::background, &EmbeddedScheme::background,
&EmbeddedScheme::sent, &EmbeddedScheme::sent,
@ -232,17 +311,19 @@ void Colorize(EmbeddedScheme &scheme, not_null<const Colorizer*> colorizer) {
&EmbeddedScheme::radiobuttonInactive &EmbeddedScheme::radiobuttonInactive
}; };
for (const auto color : colors) { for (const auto color : colors) {
Colorize(scheme.*color, colorizer); if (const auto changed = Colorize(scheme.*color, colorizer)) {
scheme.*color = changed->toRgb();
}
} }
} }
QByteArray Colorize( QByteArray Colorize(
QLatin1String hexColor, QLatin1String hexColor,
not_null<const Colorizer*> colorizer) { const Colorizer &colorizer) {
Expects(hexColor.size() == 7 || hexColor.size() == 9); Expects(hexColor.size() == 7 || hexColor.size() == 9);
auto color = qColor(str_const(hexColor.data() + 1, 6)); auto color = qColor(str_const(hexColor.data() + 1, 6));
Colorize(color, colorizer); const auto changed = Colorize(color, colorizer).value_or(color).toRgb();
auto result = QByteArray(); auto result = QByteArray();
result.reserve(hexColor.size()); result.reserve(hexColor.size());
@ -258,9 +339,9 @@ QByteArray Colorize(
addHex(code / 16); addHex(code / 16);
addHex(code % 16); addHex(code % 16);
}; };
addValue(color.red()); addValue(changed.red());
addValue(color.green()); addValue(changed.green());
addValue(color.blue()); addValue(changed.blue());
if (hexColor.size() == 9) { if (hexColor.size() == 9) {
result.append(hexColor.data()[7]); result.append(hexColor.data()[7]);
result.append(hexColor.data()[8]); result.append(hexColor.data()[8]);
@ -311,7 +392,7 @@ std::vector<EmbeddedScheme> EmbeddedThemes() {
qColor("75bfb5"), qColor("75bfb5"),
tr::lng_settings_theme_matrix, tr::lng_settings_theme_matrix,
":/gui/night-green.tdesktop-theme", ":/gui/night-green.tdesktop-theme",
qColor("3fc1b0") qColor("01ffdd")
}, },
}; };
} }

View file

@ -59,23 +59,30 @@ struct Colorizer {
Color was; Color was;
Color now; Color now;
base::flat_set<QLatin1String> ignoreKeys; base::flat_set<QLatin1String> ignoreKeys;
base::flat_map<QLatin1String, Color> keepContrast; base::flat_map<QLatin1String, std::pair<Color, Color>> keepContrast;
explicit operator bool() const {
return (hueThreshold > 0);
}
}; };
[[nodiscard]] Colorizer ColorizerFrom( [[nodiscard]] Colorizer ColorizerFrom(
const EmbeddedScheme &scheme, const EmbeddedScheme &scheme,
const QColor &color); const QColor &color);
[[nodiscard]] Colorizer ColorizerForTheme(const QString &absolutePath);
void Colorize(uchar &r, uchar &g, uchar &b, const Colorizer &colorizer);
void Colorize( void Colorize(
QLatin1String name,
uchar &r, uchar &r,
uchar &g, uchar &g,
uchar &b, uchar &b,
not_null<const Colorizer*> colorizer); const Colorizer &colorizer);
void Colorize(QImage &image, not_null<const Colorizer*> colorizer); void Colorize(QImage &image, const Colorizer &colorizer);
void Colorize(EmbeddedScheme &scheme, not_null<const Colorizer*> colorizer); void Colorize(EmbeddedScheme &scheme, const Colorizer &colorizer);
[[nodiscard]] QByteArray Colorize( [[nodiscard]] QByteArray Colorize(
QLatin1String hexColor, QLatin1String hexColor,
not_null<const Colorizer*> colorizer); const Colorizer &colorizer);
[[nodiscard]] std::vector<EmbeddedScheme> EmbeddedThemes(); [[nodiscard]] std::vector<EmbeddedScheme> EmbeddedThemes();
[[nodiscard]] std::vector<QColor> DefaultAccentColors(EmbeddedType type); [[nodiscard]] std::vector<QColor> DefaultAccentColors(EmbeddedType type);